User:Ns43110/donut.s: Difference between revisions

From NESdev Wiki
Jump to navigationJump to search
(posting donut.s source to fix dead links.)
 
(Add link to updated version)
 
(2 intermediate revisions by the same user not shown)
Line 1: Line 1:
Last Version of the NES CHR codec as used in ''Action 53 Vol. 4: Actually 54''
The NES CHR codec that used in ''Action 53 Vol. 4: Actually 54''. Encoder located in it's github repository at https://github.com/pinobatch/action53/blob/master/tools/donut.c
 
A updated version with that works in the same data format but with a more CC65 friendly subroutine at https://forums.nesdev.org/viewtopic.php?t=17313


<pre>
<pre>
Line 11: Line 13:
;
;
; Version History:
; Version History:
; 2023-09-29: Fix dumb buggy optimization in donut_bulk_load
; 2019-10-23: Slight API change to decompress_block. It returns
; 2019-10-23: Slight API change to decompress_block. It returns
;            bytes read in Y instead of adding that to stream_ptr.
;            bytes read in Y instead of adding that to stream_ptr.
Line 31: Line 34:
temp = $00  ; 16 bytes are used
temp = $00  ; 16 bytes are used


; this can be turned into a normal memory reservation
donut_block_buffer = $0100  ; 64 bytes
donut_block_buffer = $0100  ; 64 bytes


Line 52: Line 56:
   stx block_count
   stx block_count
   block_loop:
   block_loop:
     ldx #64
    ; decompress block to buffer offset by 0
     ldx #0
     jsr donut_decompress_block
     jsr donut_decompress_block
     bcs end_block_upload  ; bail on error.
     bcs end_block_upload  ; bail on error.
    ldx #64
 
    upload_loop:
     ; advance pointer by size of compressed block
      lda donut_block_buffer, x
      sta PPU_DATA
      inx
     bpl upload_loop
     tya
     tya
     ;,; clc
     ;,; clc
Line 68: Line 69:
       inc donut_stream_ptr+1
       inc donut_stream_ptr+1
     add_stream_ptr_no_inc_high_byte:
     add_stream_ptr_no_inc_high_byte:
    ; upload buffer to PPU
    ldx #0
    upload_loop:
      lda donut_block_buffer, x
      sta PPU_DATA
      inx
      cpx #64
    bcc upload_loop
     dec block_count
     dec block_count
   bne block_loop
   bne block_loop

Latest revision as of 04:02, 21 December 2023

The NES CHR codec that used in Action 53 Vol. 4: Actually 54. Encoder located in it's github repository at https://github.com/pinobatch/action53/blob/master/tools/donut.c

A updated version with that works in the same data format but with a more CC65 friendly subroutine at https://forums.nesdev.org/viewtopic.php?t=17313

; "Donut", NES CHR codec decompressor,
; Copyright (c) 2018  Johnathan Roatch
;
; Copying and distribution of this file, with or without
; modification, are permitted in any medium without royalty provided
; the copyright notice and this notice are preserved in all source
; code copies.  This file is offered as-is, without any warranty.
;
; Version History:
; 2023-09-29: Fix dumb buggy optimization in donut_bulk_load
; 2019-10-23: Slight API change to decompress_block. It returns
;             bytes read in Y instead of adding that to stream_ptr.
; 2019-02-15: Swapped the M and L bits, for conceptual consistency.
;             Also rearranged branches for speed.
; 2019-02-07: Removed "Duplicate" block type, and moved
;             Uncompressed block to below 0xc0 to make room
;             for block handling commands in the 0xc0~0xff space
; 2018-09-29: Removed block option of XORing with existing block
;             for extra speed in decoding.
; 2018-08-13: Changed the format of raw blocks to not be reversed.
;             Register X is now an argument for the buffer offset.
; 2018-04-30: Initial release.
;

.export donut_decompress_block, donut_bulk_load_ayx, donut_bulk_load_x
.export donut_block_buffer
.exportzp donut_stream_ptr

temp = $00  ; 16 bytes are used

; this can be turned into a normal memory reservation
donut_block_buffer = $0100  ; 64 bytes

.segment "ZEROPAGE"
donut_stream_ptr:       .res 2

.segment "CODE"
;;
; Decompress X*64 bytes starting at AAYY to the NES PPU via $2007 PPU_DATA
; Assumes The PPU is in forced blank, and $2006 is loaded with the desired address
;
; Trashes A, X, Y, temp 0 ~ temp 16.
.proc donut_bulk_load_ayx
  sty donut_stream_ptr+0
  sta donut_stream_ptr+1
;,; jmp donut_bulk_load
.endproc
.proc donut_bulk_load_x
PPU_DATA = $2007
block_count = temp+15
  stx block_count
  block_loop:
    ; decompress block to buffer offset by 0
    ldx #0
    jsr donut_decompress_block
    bcs end_block_upload  ; bail on error.

    ; advance pointer by size of compressed block
    tya
    ;,; clc
    adc donut_stream_ptr+0
    sta donut_stream_ptr+0
    bcc add_stream_ptr_no_inc_high_byte
      inc donut_stream_ptr+1
    add_stream_ptr_no_inc_high_byte:

    ; upload buffer to PPU
    ldx #0
    upload_loop:
      lda donut_block_buffer, x
      sta PPU_DATA
      inx
      cpx #64
    bcc upload_loop

    dec block_count
  bne block_loop
end_block_upload:
rts
.endproc

;;
; donut_decompress_block
;
; Decompresses a single variable sized block pointed to by donut_stream_ptr
; Outputing 64 bytes to donut_block_buffer offsetted by the X register.
;
; Carry flag is cleared on success and set on failure.
; The returned Y register is the number of input bytes read. (0 on failure)
; and 64 will be added to the X register. (unchanged on failure)
;
; Block header:
; LMlmbbBR
; |||||||+-- Rotate plane bits (135° reflection)
; ||||000--- All planes: 0x00
; ||||010--- L planes: 0x00, M planes:  pb8
; ||||100--- L planes:  pb8, M planes: 0x00
; ||||110--- All planes: pb8
; ||||001--- In another header byte, For each bit starting from MSB
; ||||         0: 0x00 plane
; ||||         1: pb8 plane
; ||||011--- In another header byte, Decode only 1 pb8 plane and
; ||||       duplicate it for each bit starting from MSB
; ||||         0: 0x00 plane
; ||||         1: duplicated plane
; ||||       If extra header byte = 0x00, no pb8 plane is decoded.
; ||||1x1--- Reserved for Uncompressed block bit pattern
; |||+------ M planes predict from 0xff
; ||+------- L planes predict from 0xff
; |+-------- M = M XOR L
; +--------- L = M XOR L
; 00101010-- Uncompressed block of 64 bytes (bit pattern is ascii '*' )
; Header >= 0xc0: Error, avaliable for outside processing.
; X >= 192: Also returns in Error, the buffer would of unexpectedly page warp.
;
; Trashes A, temp 0 ~ temp 15.
; bytes: 242, average cycles: 3700, cycle range: 1258 ~ 7225.
.scope donut
; The subroutine name is donut_decompress_block
plane_buffer        = temp+0 ; 8 bytes
pb8_ctrl            = temp+8
temp_y              = pb8_ctrl
even_odd            = temp+9
block_offset        = temp+10
plane_def           = temp+11
block_offset_end    = temp+12
block_header        = temp+13
is_rotated          = temp+14
;_donut_unused_temp  = temp+15  ; Used as block_count in donut_bulk_load

; these 2 routines (do_raw_block and read_plane_def_from_stream)
; are placed above decompress_block due to branch distance
do_raw_block:
  raw_block_loop:
    lda (donut_stream_ptr), y
    iny
    sta donut_block_buffer, x
    inx
    cpy #65  ; size of a raw block
  bcc raw_block_loop
  clc  ; to indicate success
exit_error:
rts

read_plane_def_from_stream:
  ror
  lda (donut_stream_ptr), y
  iny
bne plane_def_ready  ;,; jmp plane_def_ready

decompress_block:
  ldy #$00
  txa
  clc
  adc #64
  bcs exit_error
    ; If we don't exit here, xor_l_onto_m can underflow into the previous page.
  sta block_offset_end

  lda (donut_stream_ptr), y
  cmp #$c0
  bcs exit_error
    ; Return to caller to let it do the processing of headers >= 0xc0.
  iny  ; Y represents the number of successfully processed bytes.

  cmp #$2a
  beq do_raw_block
  ;,; bne do_normal_block
do_normal_block:
  sta block_header
  stx block_offset

  ;,; lda block_header
  and #%11011111
    ; The 0 are bits selected for the even ("lower") planes
    ; The 1 are bits selected for the odd planes
    ; bits 0~3 should be set to allow the mask after this to work.
  sta even_odd
    ; even_odd toggles between the 2 fields selected above for each plane.

  ;,; lda block_header
  lsr
  ror is_rotated
  lsr
  bcs read_plane_def_from_stream
  ;,; bcc unpack_shorthand_plane_def
  unpack_shorthand_plane_def:
    and #$03
    tax
    lda shorthand_plane_def_table, x
  plane_def_ready:
  ror is_rotated
  sta plane_def
  sty temp_y

  clc
  lda block_offset
  plane_loop:
    adc #8
    sta block_offset

    lda even_odd
    eor block_header
    sta even_odd

    ;,; lda even_odd
    and #$30
    beq not_predicted_from_ff
      lda #$ff
    not_predicted_from_ff:
      ; else A = 0x00

    asl plane_def
    bcc do_zero_plane
    ;,; bcs do_pb8_plane
  do_pb8_plane:
    ldy temp_y
    bit is_rotated
    bpl no_rewind_input_pointer
      ldy #$02
    no_rewind_input_pointer:
    tax
    lda (donut_stream_ptr), y
    iny
    sta pb8_ctrl
    txa

    ;,; bit is_rotated
  bvs do_rotated_pb8_plane
  ;,; bvc do_normal_pb8_plane
  do_normal_pb8_plane:
    ldx block_offset
    ;,; sec  ; C is set from 'asl plane_def' above
    rol pb8_ctrl
    pb8_loop:
      bcc pb8_use_prev
        lda (donut_stream_ptr), y
        iny
      pb8_use_prev:
      dex
      sta donut_block_buffer, x
      asl pb8_ctrl
    bne pb8_loop
    sty temp_y
  ;,; beq end_plane  ;,; jmp end_plane
  end_plane:
    bit even_odd
    bpl not_xor_m_onto_l
    xor_m_onto_l:
      ldy #8
      xor_m_onto_l_loop:
        dex
        lda donut_block_buffer, x
        eor donut_block_buffer+8, x
        sta donut_block_buffer, x
        dey
      bne xor_m_onto_l_loop
    not_xor_m_onto_l:

    bvc not_xor_l_onto_m
    xor_l_onto_m:
      ldy #8
      xor_l_onto_m_loop:
        dex
        lda donut_block_buffer, x
        eor donut_block_buffer+8, x
        sta donut_block_buffer+8, x
        dey
      bne xor_l_onto_m_loop
    not_xor_l_onto_m:

    lda block_offset
    cmp block_offset_end
  bcc plane_loop
  ldy temp_y
  tax  ;,; ldx block_offset_end
  clc  ; to indicate success
rts

do_zero_plane:
  ldx block_offset
  ldy #8
  fill_plane_loop:
    dex
    sta donut_block_buffer, x
    dey
  bne fill_plane_loop
beq end_plane  ;,; jmp end_plane

do_rotated_pb8_plane:
  ldx #8
  buffered_pb8_loop:
    asl pb8_ctrl
    bcc buffered_pb8_use_prev
      lda (donut_stream_ptr), y
      iny
    buffered_pb8_use_prev:
    dex
    sta plane_buffer, x
  bne buffered_pb8_loop
  sty temp_y
  ldy #8
  ldx block_offset
  flip_bits_loop:
    asl plane_buffer+0
    ror
    asl plane_buffer+1
    ror
    asl plane_buffer+2
    ror
    asl plane_buffer+3
    ror
    asl plane_buffer+4
    ror
    asl plane_buffer+5
    ror
    asl plane_buffer+6
    ror
    asl plane_buffer+7
    ror
    dex
    sta donut_block_buffer, x
    dey
  bne flip_bits_loop
beq end_plane  ;,; jmp end_plane

shorthand_plane_def_table:
  .byte $00, $55, $aa, $ff
.endscope

donut_decompress_block = donut::decompress_block