[stella] Drawing Sprites

Subject: [stella] Drawing Sprites
From: Paul Slocum <paul-stella@xxxxxxxxxxxxxx>
Date: Sun, 17 Feb 2002 14:28:17 -0600
My Marble Craze kernal is coming along pretty well. I've got a full width asymmetrical playfield, two sprites, paddle reads, and independent playfield shading for the left and right sides (to indicate slopes up and down.)

But I'm having problems figuring out how to tell the program when to draw the sprites. Just to get the sprites on the screen, I used the "zero out a page of memory except for the sprite data" technique. I went through the Maze Craze code enough to understand how most of the kernal works, and best I can tell, it uses the zero-out memory sprite technique itself! I've heard you guys say there are better ways to display the sprites. Since I'm planning on using bankswitching, it won't kill me to have the zero'd out memory, but it will limit the sprite animation I can do.

Here's an overview: My kernal routine loops 12 times and each loop draws 13 lines of the game screen. On each line I draw the two sprites using something like:

	lda (pMarble),y		;4
	sta GRP1		;3

Right now there's just one pointer (pMarble) but I will later add pMarble1 and pMarble2. My plan was to update the pMarbleN pointer for each sprite once each time through the loop (so every 13 lines). Either pointing it to the correct offset from the sprite data or to a small zero'd out piece of memory. This would still require zero'd out memory, but a LOT less.

I do have a decent amount of extra cycles left, but most of the ideas I come up with end up using too many cycles or too much memory. The code is quite a mess, but I attached it. This is a tough problem, and I'm not sure if anyone will have time to take a look at it, but thanks in advance if you can.

-Paul






;---------------------------------------------------------------------------
;
; Song Player by Paul Slocum
;
;---------------------------------------------------------------------------

	processor 6502	
	include vcs.h	


;---------------------------------------------------------------------------
; Constants
;---------------------------------------------------------------------------
SETUPDELAY equ #255

;---------------------------------------------------------------------------
; RAM Variables 
;---------------------------------------------------------------------------

textColor equ $80

playField equ textColor + 1 ; 60 bytes (12 lines * 5)
pfBuffer equ playField + 72	; 7 bytes

pMarble equ pfBuffer + 8; 2 bytes
pfColor equ pMarble + 2

padVal1 equ pfColor + 1
padVal2 equ padVal1 + 1

stack equ padVal2 + 1

frame equ stack + 1

;---------------------------------------------------------------------------
; Song player variables

temp equ frame + 1

; 16 bit temp
temp16L equ temp + 1
temp16H equ temp16L + 1

note1 equ temp16H + 1
note2 equ note1 + 1

vol1 equ note2 + 1
vol2 equ vol1 + 1

sound1 equ vol2 + 1
sound2 equ sound1 + 1

; Metrenome stuff
beat equ sound2 + 1
tempoCount equ beat + 1
measure equ tempoCount + 1






;---------------------------------------------------------------------------
;---------------------------------------------------------------------------
	org $F000
;---------------------------------------------------------------------------
;---------------------------------------------------------------------------

marbleData
	byte 0,0,0,0,0,0,0,0

	byte #%00000000
	byte #%00000000
	byte #%00000000
	byte #%00111100
	byte #%01111110
	byte #%11111111
	byte #%11111111
	byte #%11111111
	byte #%11111111
	byte #%01111110
	byte #%01111110
	byte #%00111100


	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0

	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0

	align 256

marbleData2
	byte 0,0,0,0,0,0,0,0

	byte #%00111100
	byte #%01111110
	byte #%01111110
	byte #%11111111
	byte #%11111111
	byte #%11111111
	byte #%11111111
	byte #%11111111
	byte #%11111111
	byte #%01111110
	byte #%01111110
	byte #%00111100


	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0

	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0
	byte 0,0,0,0,0,0,0,0

				
;---------------------------------------------------------------------------
; Start of Program
;---------------------------------------------------------------------------
; Clear memory, locate character graphics positions and such,
; initialize key memory values, start GameLoop.
;-------------------------------------------------------------
Start
	sei  	
	cld  		
	ldx #$FF
	txs  		
	lda #0
clear   
	sta 0,x
	dex
	bne clear	


;---------------------------------------------------------------------------
; Initialize variables
;---------------------------------------------------------------------------

	; Set initialization flag.
	; This stays set until the first frame is drawn.
	lda #$0f	;set text color to white
	sta COLUP0
	sta COLUP1	



;--------------------------------------------------------------------------
; GameLoop
;--------------------------------------------------------------------------
GameLoop
	inc frame	

	jsr VSync 	;start vertical retrace

	jsr VBlank    	; spare time during screen blank
	jsr drawScreen	; draw one screen
	jsr overscan	; do overscan

	jmp GameLoop    ;back to top



;--------------------------------------------------------------------------
; VSync
;--------------------------------------------------------------------------
VSync
	lda #2		;bit 1 needs to be 1 to start retrace
	sta VSYNC	;start retrace
	sta WSYNC 	;wait a few lines
	sta WSYNC 
	lda #44		;prepare timer to exit blank period (44)
	sta TIM64T	;turn it on
	sta WSYNC 	;wait one more
	sta VSYNC 	;done with retrace, write 0 to bit 1

	rts ; VSync



;--------------------------------------------------------------------------
; VBlank
;--------------------------------------------------------------------------
; Handle user input and display setup during the VBLANK period
;--------------------------------------------------------------------------
VBlank
;	jsr songPlayer
;	jsr notePlayer

	ldy #72
pfCopy
	lda level1-1,y
	sta playField-1,y
	dey
	bne pfCopy

	rts ; VBlank

;--------------------------------------------------------------------------
; Overscan
;--------------------------------------------------------------------------
overscan

	sta WSYNC	
	lda #33		; Use the timer to make sure overscan takes (34)
	sta TIM64T	; 30 scan lines.  29 scan lines * 76 = 2204 / 64 = 34.4

endOS	
	lda INTIM	; We finished, but wait for timer
	bne endOS	; by looping till zero
	
	sta WSYNC	; End last scanline

	lda #$82
	sta VBLANK
	lda #$02
	sta VBLANK

	rts	; overscan





;--------------------------------------------------------------------------
; Draw Screen
;--------------------------------------------------------------------------
drawScreen

	sta WSYNC
	lda (temp),x
	lda (temp),x
	lda (temp),x
	lda (temp),x
	lda temp
	sta RESP0
	lda (temp),x
	lda (temp),x
	lda temp
	sta RESBL
	nop
	lda temp
	sta RESP1	
	
	lda #%00100000
	sta HMP0
	lda #%00010000
	sta HMBL
	sta WSYNC
	sta HMOVE

timer
    LDA INTIM
    BNE timer ;Loops until the timer is done - that means we're
                   ;somewhere in the last line of vertical blank.

    STA WSYNC      ;End the current scanline - the last line of VBLANK.
    STA VBLANK     ;End the VBLANK period.  The TIA will display stuff
                   ;starting with the next scanline.  We do have 23 cycles
                   ;of horizontal blank before it displays anything.

	lda #%10010010
	sta GRP0
	
	lda #%00000111
	sta NUSIZ0
	sta NUSIZ1
	
	
	lda #0
	sta GRP0
	sta GRP1
	sta COLUP0
	sta COLUP1
	lda #255
	sta ENABL
	sta WSYNC
	lda #$0F
    STA COLUPF
	
    LDY #6      ;We're going to use Y to count lines.
scoreLoop
	lda scoreData1,y
	sta PF1
	lda scoreP0,y
	sta GRP0
	lda scoreData2,y
	sta PF2
	lda scoreP1,y
	sta GRP1
    STA WSYNC      ;Wait for end of scanline
    DEY
    BNE scoreLoop   ;Count scanlines.

	lda #0
	sta PF1
	sta PF2

	sta WSYNC
	sta WSYNC

    LDY #6      ;We're going to use Y to count scanlines.
scoreLoop2
	lda scoreData1,y
	sta PF1
	lda scoreP0,y
	sta GRP0
	lda scoreData2,y
	sta PF2
	lda scoreP1,y
	sta GRP1
    STA WSYNC      ;Wait for end of scanline
    DEY
    BNE scoreLoop2   ;Count scanlines.

	lda #0
	sta PF1
	sta PF2

	; make room for future status ares
	sta WSYNC
	sta WSYNC
	sta WSYNC
	sta WSYNC
	sta WSYNC
	sta WSYNC
	sta WSYNC
	sta WSYNC
	sta WSYNC
	sta WSYNC
	sta WSYNC
	sta WSYNC
	sta WSYNC
	sta WSYNC
	lda #255
	sta PF0
	sta PF1
	sta PF2

	sta WSYNC
	lda #0		;2
	sta PF0		;3
	sta PF1		;3
	sta PF2		;3


	; *********************************************************
	; Start of kernal
	; *********************************************************
	; pfBuffer
	; 0 = PF0.1
	; 1 = PF1.1
	; 2 = PF2.1
	; 3 = PF0.2
	; 4 = PF1.2
	; 5 = PF2.2
	; 6 = PFCOLOR1
	; 7 = PFCOLOR2
	;
	;  0  Hblank  22 PF0 28   PF1   38   PF2   48 PF0 56   PF1   66   PF2   76
	;  |----22----||--6--||---10----||---10----||--6--||---10----||---10----||
	;               4---7  7-------0  0-------7  4---7  7-------0  0-------7

	;  1 setup
	;  2 colors
	;  3 read paddle1
	;  4 colors
	;  5 read paddle2
	;  6 colors
	;  7 more setup
	;  8 colors
	;  9 read paddle1
	; 10 colors
	; 11 read paddle2
	; 12 colors
	; 13 setup to loop

	; save stack
	tsx
	stx stack

	lda #$54
	sta pfColor

	sta WSYNC
	lda #0		;2
	sta NUSIZ0	;3
	sta NUSIZ1	;3
	sta PF1		;3
	sta PF2		;3
	lda #$84	;2
	sta COLUPF	;3


	ldx #12			;2 = 18

	; Use Y to track the actual line
	ldy #156		;2 20

	lda #$56		;PF color 2
	sta pfBuffer+7
	
	lda frame
	and #%00000001
	beq shadow

	lda #<marbleData	;2
	sta pMarble			;3
	lda #>marbleData	;2
	sta pMarble+1		;3

	lda #$0A		;2
	sta COLUP0		;3
	sta COLUP1		;3
	jmp noShadow

shadow
	lda #<marbleData2	;2
	sta pMarble			;3
	lda #>marbleData2	;2
	sta pMarble+1		;3

	lda #$08		;2
	sta COLUP0		;3
	sta COLUP1		;3

noShadow




	; THINGS TO ADD
	; 
	; Dual PF Color Array
	; Sprite Offsets
	; Shaded balls


;       LDA    INPT0,X 
;       BMI    LF511   
;       STY    $DB,X   


scanLoop2


	lda pfColor
	sta COLUPF

	lda playField-1,x	;4 76

	; 1) setup
	;---------------------------------------

	sta PF0				;3
	sta pfBuffer		;3
	lda (pMarble),y		;5*
	sta GRP0			;3
	lda playField+11,x	;4
	sta PF1				;3
	lda playField+23,x	;4
	sta PF2				;3

	lda (pMarble),y		;5*
	sta GRP1			;3

	lda pfBuffer		;3
	asl					;2
	asl					;2
	asl					;2
	asl					;2
	sta PF0				;3 48
	sta pfBuffer+3		;3

	lda playField+35,x	;4
	sta PF1				;3 55
	lda playField+47,x	;4
	sta PF2				;3
	sta pfBuffer+5		;3
	dey				;2
	lda (pMarble),y		;5*
	
	; 2) colors
	;---------------------------------------
	sta GRP0			;3

	lda playField+59,x	;4 66 
	sta COLUPF			;3
	sta pfBuffer+6		;3 

	lda pfBuffer		;3
	sta PF0				;3
	lda playField+11,x	;4
	sta PF1				;3
	lda playField+23,x	;4
	sta PF2				;3 34

	lda (pMarble),y		;4
	sta GRP1			;3
	
	
	lda pfBuffer+3		;3
	sta PF0				;3 52
	lda playField+35,x	;4
	sta PF1				;3

	lda pfBuffer+7		;3
	sta COLUPF			;3

	lda pfBuffer+5		;4
	sta PF2				;3

	dey					;2
	lda (pMarble),y		;4*
	sta GRP0			;3

	; 3) paddles
	;---------------------------------------

	lda pfBuffer		;3
	sta PF0				;3
	lda pfColor			;3
	sta COLUPF			;3

	lda playField+11,x	;4
	sta PF1				;3
	sta pfBuffer+1		;3

	lda playField+23,x	;4
	sta PF2				;3

	lda (pMarble),y		;4
	sta GRP1			;3 39
	
	nop
;	nop

	lda pfBuffer+3		;3
	sta PF0				;3 52

	lda playField+35,x	;4
	sta PF1				;3

	lda pfBuffer+5		;4
	sta PF2				;3

	dey					;2
	lda (pMarble),y		;4*
	sta GRP0			;3 68

;	lda INPT0			;3
;	bmi paddles1		;2 or 3
;	sty padVal1			;3
;paddles1

	lda (temp),x		; 6 simulate max paddle read cycles
	nop					; 2

	lda pfBuffer		;3
	sta PF0				;3

	sta WSYNC

	; 4) colors
	;---------------------------------------

	lda pfBuffer+6		;3
	sta COLUPF			;3

	lda pfBuffer+1		;4
	sta PF1				;3

	lda playField+23,x	;4
	sta PF2				;3 29
	sta pfBuffer+2		;3

	lda (pMarble),y		;4
	sta GRP1			;3
	

	lda pfBuffer+3		;3
	sta PF0				;3 52

	lda playField+35,x	;4
	sta PF1				;3

	lda pfBuffer+7		;3
	sta COLUPF			;3

	lda pfBuffer+5		;4
	sta PF2				;3

	dey					;2

	lda (pMarble),y		;4*
	sta GRP0			;3

	lda pfBuffer		;3
	sta PF0				;3

	; 5) paddles
	;---------------------------------------

	lda pfBuffer		;3
	sta PF0				;3

	lda pfColor			;3
	sta COLUPF			;3

	lda pfBuffer+1		;3
	sta PF1				;3

	lda pfBuffer+2		;3
	sta PF2				;3 29

	lda (pMarble),y		;4
	sta GRP1			;3
	
	lda pfBuffer+3		;3
	sta PF0				;3 52

	lda playField+35,x	;4
	sta PF1				;3
	sta pfBuffer+4

	lda pfBuffer+5		;4
	sta PF2				;3

	dey					;2
	lda (pMarble),y		;4*
	sta GRP0			;3
	lda pfBuffer		;3
	sta PF0				;3
	
	lda (temp),x		; 6 simulate max paddle-read cycles
	nop					; 2

	sta WSYNC

	; 6) colors
	;---------------------------------------

	lda pfBuffer+6		;3
	sta COLUPF			;3

	txs
	ldx pfBuffer+1		;3
	stx PF1				;3

	lda pfBuffer+2		;3
	sta PF2				;3

	lda (pMarble),y		;4
	sta GRP1			;3

	dey					;2
	
	lda pfBuffer+3		;3
	sta PF0				;3

	lda pfBuffer+4		;3
	sta PF1				;3

	lda pfBuffer+7		;3
	sta COLUPF			;3

	lda pfBuffer+5		;4
	sta PF2				;3


	lda (pMarble),y		;4*
	sta GRP0			;3

	lda pfBuffer		;3
	sta PF0				;3

	; 7) more setup
	;---------------------------------------

	lda pfBuffer		;3
	sta PF0				;3

	lda pfColor			;3
	sta COLUPF			;3

	stx PF1				;3

	lda pfBuffer+2		;3
	sta PF2				;3

	lda (pMarble),y		;4
	sta GRP1			;3

	nop					;10
	nop
	nop
	nop
	nop
	
	lda pfBuffer+3		;3
	sta PF0				;3

	lda pfBuffer+4		;3
	sta PF1				;3

	lda pfBuffer+5		;4
	sta PF2				;3

	dey					;2
	lda (pMarble),y		;4*
	sta GRP0			;3
	lda pfBuffer		;3
	sta PF0				;3
	
	nop					;16
	nop
	nop
	nop
	nop
	nop
	nop
	nop

	; 8) colors
	;---------------------------------------

	lda pfBuffer+6		;3
	sta COLUPF			;3

	stx PF1				;3

	lda pfBuffer+2		;3
	sta PF2				;3

	lda (pMarble),y		;4
	sta GRP1			;3

	dey					;2
	
	lda pfBuffer+3		;3
	sta PF0				;3

	lda pfBuffer+4		;3
	sta PF1				;3


	lda pfBuffer+7		;3
	sta COLUPF			;3

	lda pfBuffer+5		;4
	sta PF2				;3

	lda (pMarble),y		;4*
	sta GRP0			;3

	lda pfBuffer		;3
	sta PF0				;3

	; 9) paddles
	;---------------------------------------

	lda pfBuffer		;3
	sta PF0				;3

	lda pfColor			;3
	sta COLUPF			;3

	stx PF1				;3

	lda pfBuffer+2		;3
	sta PF2				;3

	lda (pMarble),y		;4
	sta GRP1			;3

	nop					;10
	nop
	nop
	nop
	nop
	
	lda pfBuffer+3		;3
	sta PF0				;3

	lda pfBuffer+4		;3
	sta PF1				;3

	lda pfBuffer+5		;4
	sta PF2				;3

	dey					;2
	lda (pMarble),y		;4*
	sta GRP0			;3
	lda pfBuffer		;3
	sta PF0				;3
	
	lda (temp),x		; 6 simulate max paddle-read cycles
	nop					; 2

	sta WSYNC


	; 10) colors
	;---------------------------------------

	lda pfBuffer+6		;3
	sta COLUPF			;3

	stx PF1				;3

	lda pfBuffer+2		;3
	sta PF2				;3

	lda (pMarble),y		;4
	sta GRP1			;3

	nop
	nop
	nop
;	nop
	
	lda pfBuffer+3		;3
	sta PF0				;3

	lda pfBuffer+4		;3
	sta PF1				;3

	dey					;2

	lda pfBuffer+7		;3
	sta COLUPF			;3

	lda pfBuffer+5		;4
	sta PF2				;3

	lda (pMarble),y		;4*
	sta GRP0			;3

	lda pfBuffer		;3
	sta PF0				;3

;	sta WSYNC

	; 11) paddles
	;---------------------------------------

	lda pfBuffer		;3
	sta PF0				;3

	lda pfColor			;3
	sta COLUPF			;3

	stx PF1				;3

	lda pfBuffer+2		;3
	sta PF2				;3

	lda (pMarble),y		;4
	sta GRP1			;3

	nop
	nop
	nop
	nop

	
	lda pfBuffer+3		;3
	sta PF0				;3

	lda pfBuffer+4		;3
	sta PF1				;3

	lda pfBuffer+5		;4
	sta PF2				;3

	dey					;2
	lda (pMarble),y		;4*
	sta GRP0			;3
	lda pfBuffer		;3
	sta PF0				;3
	
	lda (temp),x		; 6 simulate max paddle-read cycles
	nop					; 2

	sta WSYNC

	; 12) colors
	;---------------------------------------

	lda pfBuffer+6		;3
	sta COLUPF			;3

	stx PF1				;3

	lda pfBuffer+2		;3
	sta PF2				;3

	lda (pMarble),y		;4
	sta GRP1			;3

	nop
	nop
	nop
	
	lda pfBuffer+3		;3
	sta PF0				;3

	lda pfBuffer+4		;3
	sta PF1				;3

	dey					;2

	lda pfBuffer+7		;3
	sta COLUPF			;3

	lda pfBuffer+5		;4
	sta PF2				;3


	lda (pMarble),y		;4*
	sta GRP0			;3

	lda pfBuffer		;3
	sta PF0				;3

	sta WSYNC

	; 13) setup for loop
	;---------------------------------------

	lda pfColor			;3
	sta COLUPF			;3

	stx PF1				;3
	tsx					;2

	lda pfBuffer+2		;3
	sta PF2				;3

	;lda temp
	lda (pMarble),y
	sta GRP1

	nop
	dey

	lda pfBuffer+3		;3
	sta PF0				;3
	lda pfBuffer+4		;4
	sta PF1				;3
	lda pfBuffer+5		;3
	sta PF2				;3


    dex					;2
    beq quitScanLoop2   ;2
	jmp scanLoop2		;3 must be 70

quitScanLoop2
	sta WSYNC

	lda #$0F
	sta COLUPF
	lda #255
	sta PF0
	sta PF1
	sta PF2
	
	; Fill the remaining scanlines
	sta WSYNC
	lda #0
	sta PF0
	sta ENABL
	sta PF1
	sta PF2

	sta WSYNC
	sta WSYNC
	sta WSYNC
	sta WSYNC
	sta WSYNC

	; restore stack
	ldx stack
	txs

    RTS

timeData1
scoreP1
scoreP0
	byte #%00000000
	byte #%00000000
	byte #%10000010
	byte #%00000000
	byte #%00000000
	byte #%10010000
	byte #%00000000
	byte #%00000000

scoreData1
	byte #%00000000
	byte #%10110010
	byte #%10110010
	byte #%10110010
	byte #%10110110
	byte #%10110110
	byte #%10110110
	byte #%10110110

scoreData2
	byte #%00000000
	byte #%00010011
	byte #%00010011
	byte #%00010011
	byte #%00010011
	byte #%00010001
	byte #%00011011
	byte #%00011011

	
level1
	;PF0
	byte %01100000
	byte %11100000
	byte %00000000
	byte %00000000
	byte %00000000
	byte %00000000
	byte %00000000
	byte %00001111
	byte %00000101
	byte %00000000
	byte %00000000
	byte %00000000

	;PF1.1
	byte %00000000
	byte %11111111
	byte %00000000
	byte %00000000
	byte %00000000
	byte %00000000
	byte %00000000
	byte %00000000
	byte %00000000
	byte %00111111
	byte %00110000
	byte %00110000

	;PF2.1
	byte %00000000
	byte %00111111
	byte %00110000
	byte %00110000
	byte %00110000
	byte %00111111
	byte %00000011
	byte %01111111
	byte %01100000
	byte %01111111
	byte %00000000
	byte %00000000
	
	;PF1.1
	byte %01100000
	byte %01100000
	byte %01111000
	byte %00011000
	byte %00011110
	byte %00000110
	byte %00000111
	byte %00000001
	byte %00000001
	byte %00000000
	byte %00000000
	byte %00000000

	;PF2.2
	byte %00000000
	byte %00000000
	byte %00000000
	byte %00000000
	byte %00000000
	byte %00000000
	byte %00000001
	byte %00000001
	byte %00000111
	byte %00000110
	byte %00011110
	byte %00011000

	; colors
	byte $52
	byte $54
	byte $52
	byte $52
	byte $52
	byte $54
	byte $56
	byte $54
	byte $52
	byte $54
	byte $54
	byte $54


;	include mtitle.h

;	include songplay.h


;---------------------------------------------------------------------------
; Program Startup
;---------------------------------------------------------------------------
	org $FFFC
	.word Start
	.word Start

Attachment: marble.bin
Description: Binary data

Current Thread