; Copyright 1996 Acorn Computers Ltd
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
;     http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
;
; > $.Source.VduPointer

; mjs Sep 2000
;
; kernel/HAL split
; display pointer updating is no longer VIDC/IOMD specific
;

; *****************************************************************************
;
;       DoPointerStuff - Entry point for OSWORD nn
;
; in:   R1 -> control block
;       [R1, #0] : Reason code
;
;       Reason code 0 - Define pointer size, shape and active point
;
;       [R1, #1] : Shape number (1..4)
;       [R1, #2] : Width (w) in bytes (0..8)
;       [R1, #3] : Height (h) in pixels (0..32)
;       [R1, #4] : ActiveX in pixels from left (0..w*4-1)
;       [R1, #5] : ActiveY in pixels from top (0..h-1)
;       [R1, #6..9] : Pointer (P) to data
;       [P, #0..w*h-1] : Data bytes in rows from top to bottom,
;       left to right in each row.
;
;       Reason code 1 - Define mouse coordinate bounding box
;
;       [R1, #1..2] : left      ; all treated as
;       [R1, #3..4] : bottom    ; signed 16-bit values,
;       [R1, #5..6] : right     ; relative to screen origin at the time
;       [R1, #7..8] : top       ; the command is issued
;
;       If (left > right) or (bottom > top) then the command is ignored
;       An infinite box can be obtained by setting
;       left=&8000, right=&7FFF, bottom=&8000, top=&7FFF
;
;       If the current mouse position is outside the box, it is moved to
;       the nearest point inside the box
;
;       The mouse buffer is NOT flushed - any buffered coords will be moved
;       inside the bounding box when they are read.
;
;       Reason code 2 - Define mouse multipliers
;
;       [R1, #1] : X multiplier         ; both treated as
;       [R1, #2] : Y multiplier         ; signed 8-bit values
;
;       Reason code 3 - Set mouse position
;
;       [R1, #1..2] : X position        ; both treated as
;       [R1, #3..4] : Y position        ; signed 16-bit values
;       The mouse buffer is flushed
;
;       Reason code 4 - Read mouse position (not buffered)
;
;       out: [R1, #1..2] : X position   ; both treated as
;            [R1, #3..4] : Y position   ; signed 16-bit values
;
;       Reason code 5 - Set pointer position
;
;       [R1, #1..2] : X position        ; both treated as
;       [R1, #3..4] : Y position        ; signed 16-bit values
;
;       Reason code 6 - Read pointer position
;
;       out: [R1, #1..2] : X position   ; both treated as
;            [R1, #3..4] : Y position   ; signed 16-bit values
;

DoPointerStuff ROUT
        LDRB    R0, [R1, #0]

        CMP     R0, #7

        LDRCC   PC, [PC, R0, LSL #2]
        MOV     PC, R14                 ; ***** WHY NO ERROR????????
        DCD     DoDefinePointer
        DCD     DoMouseBox
        DCD     SetMouseMult
        DCD     SetMousePosn
        DCD     ReadMousePosn
        DCD     SetPointerPosn
        DCD     ReadPointerPosn

; *****************************************************************************

DoDefinePointer

        ; We allow interrupts during time we copy shape, but we copy into a
        ; holding descriptor, so shape will never be displayed (whether its the
        ; current one, or becomes the current one with an OSByte 6A) until the
        ; vsync after we have a complete definition.
        ;
        ; We have two holding buffers, so that we can always choose a holding
        ; buffer that is not currently being used for display by the HAL,
        ; despite multiple definitions between vsyncs. This all assumes we
        ; are never re-entered, but the documentation for OS_Word 21,0 says
        ; re-entrancy undefined anyway - should really say not re-entrant.

        Push    "R1-R7, R14"

        ; interrupts still off for critical choosing of buffer
        ;
        ADD     R6, WsPtr, #PointerShapesH
        MOV     R7, #0                          ; try holding shape 1
        LDR     R14, [R6, R7, LSL #2]           ; R14 -> shape
        LDR     R2, [R14, #PointerBuffLA]       ; shape buffer we propose to use
        LDR     R0, [WsPtr, #PointerShapeLA]    ; shape buffer owned by HAL
        TEQ     R0, R2                          ; identical?
        MOVEQ   R7, #1                          ; alright then, holding shape 2
        LDREQ   R14, [R6, R7, LSL #2]           ; R14 -> shape

        ; now R7 = holding shape index (0,1), R14 -> shape, not owned by HAL

        CLRPSR  I_bit, R0                       ; re-enable interrupts

        LDRB    R6, [R1, #1]                    ; shape number we're defining
        SUB     R6, R6, #1
        CMP     R6, #4                          ; now in range 0..3 ?
        BCS     %FT90                           ; bad shape number

        LDRB    R0, [R1, #2]                    ; R0 = width (bytes)
        LDRB    R2, [R1, #3]                    ; R2 = height
        LDRB    R3, [R1, #4]                    ; R3 = ActiveX
        LDRB    R4, [R1, #5]                    ; R4 = ActiveY

        CMP     R2, #0                          ; C=1 if EQ
        STREQB  R2, [R14, #PointerWidth]
        STREQB  R2, [R14, #PointerHeight]
        BEQ     %FT80                           ; empty shape (off)

        CMP     R0, #0                          ; C=1 if EQ
        STREQB  R0, [R14, #PointerWidth]
        STREQB  R0, [R14, #PointerHeight]
        CMPNE   R0, #8+1
        BCS     %FT90                           ; bad width

        CMP     R2, #32+1                       ; C=1 => bad height
        CMPCC   R3, R0, LSL #2                  ; ActiveX >= (width * 4) ?
        CMPCC   R4, R2                          ; ActiveY >= height

        BCS     %FT90                           ; bad definition

        STRB    R0, [R14, #PointerWidth ]       ; actual width in bytes, before padding to constant 8
        STRB    R2, [R14, #PointerHeight]
        STRB    R3, [R14, #PointerActiveX]
        STRB    R4, [R14, #PointerActiveY]

        ADD     R4, R1, #6
        LDW     R1, R4, R3, R5                  ; load word from
                                                ; unknown alignment
; Now R1 -> user's data

        LDR     R3, [R14, #PointerBuffLA]       ; R3 -> buffer to receive shape
20
        ADD     R4, R3, R0                      ; terminating R3 for this row
30
        LDRB    R5, [R1], #1
40
        STRB    R5, [R3], #1                    ; store to buffer
        CMP     R3, R4                          ; still within user data
        BCC     %BT30                           ; for this row ?

; now fill up rest of row

        MOV     R5, #0
        TST     R3, #7                          ; are we on a multiple of 8
        BNE     %BT40                           ; no, then store 0

        SUBS    R2, R2, #1                      ; done all rows ?
        BNE     %BT20                           ; no, then loop

80
        ; we now have a completely defined shape in a holding buffer
        ;
        PHPSEI  R0                              ; disable interrupts for critical shape logic
        ADD     R3, WsPtr, #PointerShapes
        ADD     R4, WsPtr, #PointerShapesH
        LDR     R1, [R3, R6, LSL #2]            ; swap the holding shape (R7=0,1) into
        LDR     R2, [R4, R7, LSL #2]            ; the shape we've just defined (R6 = 0..3)
        STR     R2, [R3, R6, LSL #2]
        STR     R1, [R4, R7, LSL #2]
        PLP     R0                              ; restore interrupts
90
        Pull    "R1-R7,PC"


; *****************************************************************************
;
;       SetMouseRectangle - Called on mode change to set appropriate mouse
;                           rectangle and mouse position
;
; in:   WsPtr -> VDWS
;

SetMouseRectangle ROUT
        Push    R14

        ASSERT  DisplayYWindLimit = DisplayXWindLimit +4
        ASSERT  DisplayXEigFactor = DisplayXWindLimit +8
        ASSERT  DisplayYEigFactor = DisplayXWindLimit +12

        ADD     R2, WsPtr, #DisplayXWindLimit
        LDMIA   R2, {R2-R5}

        ADD     R2, R2, #1              ; XWindLimit+1
        MOV     R2, R2, LSL R4          ; (XWindLimit+1) << XEigFactor
        SUB     R4, R2, #1              ; ((XWindLimit+1) << XEigFactor)-1
        MOV     R2, R2, LSR #1          ; centre x of window

        ADD     R3, R3, #1              ; YWindLimit+1
        MOV     R3, R3, LSL R5          ; (YWindLimit+1) << YEigFactor
        SUB     R5, R3, #1              ; ((YWindLimit+1) << YEigFactor)-1
        MOV     R3, R3, LSR #1          ; centre y of window

        BL      SetMousePosnRegs

        MOV     R2, #0                  ; left = 0
        MOV     R3, #0                  ; bottom = 0

        Push    "R1-R6"
        B       DoMouseBoxRegs


DoMouseBox ROUT
        Push    "R1-R6, R14"

      [ NoARMv6 :LOR: NoUnaligned
        LDRB    R2, [R1, #1]            ; R2 = left
        LDRB    R0, [R1, #2]
        ORR     R2, R2, R0, LSL #8

        LDRB    R3, [R1, #3]            ; R3 = bottom
        LDRB    R0, [R1, #4]
        ORR     R3, R3, R0, LSL #8

        LDRB    R4, [R1, #5]            ; R4 = right
        LDRB    R0, [R1, #6]
        ORR     R4, R4, R0, LSL #8

        LDRB    R5, [R1, #7]            ; R5 = top
        LDRB    R0, [R1, #8]
        ORR     R5, R5, R0, LSL #8
      |
        ; Use unaligned loads from ARMv6
        LDRH    R2, [R1, #1]            ; R2 = left
        LDRH    R3, [R1, #3]            ; R3 = bottom
        LDRH    R4, [R1, #5]            ; R4 = right
        LDRH    R5, [R1, #7]            ; R5 = top
      ]

DoMouseBoxRegs

; now add on graphics origin

        LDR     R0, [WsPtr, #OrgX]
        ADD     R2, R2, R0
        ADD     R4, R4, R0
        LDR     R0, [WsPtr, #OrgY]
        ADD     R3, R3, R0
        ADD     R5, R5, R0

; now sign extend all coords

      [ NoARMv6
        MOV     R2, R2, LSL #16
        MOV     R2, R2, ASR #16
        MOV     R3, R3, LSL #16
        MOV     R3, R3, ASR #16
        MOV     R4, R4, LSL #16
        MOV     R4, R4, ASR #16
        MOV     R5, R5, LSL #16
        MOV     R5, R5, ASR #16
      |
        ; ARMv6 lets you do this using SXTH
        SXTH    R2, R2
        SXTH    R3, R3
        SXTH    R4, R4
        SXTH    R5, R5
      ]

; now check right >= left and top >= bottom

        CMP     R4, R2
        CMPGE   R5, R3
        BLT     %FT10                   ; bad definition

; everything seems OK, so disable IRQs while we update vars

        MRS     R14, CPSR
        ORR     R0, R14, #I32_bit
        MSR     CPSR_c, R0

        Push    R11
        LDR     R11, =ZeroPage+KeyWorkSpace

        ADR     R0, MouseBounds
        STMIA   R0, {R2-R5}

; check mouse position is within box

        LDR     R0, MouseX
        CMP     R0, R2                  ; if X < left
        STRLT   R2, MouseX              ; then X := left
        CMP     R4, R0                  ; if right < X
        STRLT   R4, MouseX              ; then X := right

        LDR     R0, MouseY
        CMP     R0, R3                  ; if Y < bottom
        STRLT   R3, MouseY              ; then Y := bottom
        CMP     R5, R0                  ; if top < Y
        STRLT   R5, MouseY              ; then Y := top

        Pull    R11

        MSR     CPSR_c, R14             ; restore old IRQ state
10
        Pull    "R1-R6, PC"

; *****************************************************************************
;
;       UpdatePointer - Called on vsync to update pointer position
;
; in:   WsPtr (R12) -> VduDriverWorkSpace
;       IRQs disabled, but can be enabled (n.b. may be in IRQ mode)
;
UpdatePointer ROUT
        Push    "R14"

        LDRB    R5, [WsPtr, #PointerShapeNumber]

        TST     R5, #&80                   ; pointer unlinked if bit 7 set

        LDREQ   R6, MouseX
        STREQ   R6, [WsPtr, #PointerX]
        LDREQ   R6, MouseY
        STREQ   R6, [WsPtr, #PointerY]

        ANDS    R5, R5, #&7F                 ; clear bit 7 and set Z if 0 ie off
        BNE     %FT20

10
        MOV     R0, #0                       ; flags = 0 (pointer off)
        MOV     R1, #0                       ; x = 0
        MOV     R2, #0                       ; y = 0
        MOV     R3, #0                       ; shape descriptor = NULL
        STR     R3, [WsPtr, #PointerShapeLA] ; NULL passed as last buffer address
        B       %FT40

20
        ADD     R3, WsPtr, #PointerShapes-4
        LDR     R3, [R3, R5, LSL #2]         ; R3 -> current shape block (R5 = shape 1..4)

        LDRB    R0, [R3, #PointerHeight]     ; height of 0 switches pointer off
        TEQ     R0, #0
        BEQ     %BT10

        MOV     R0, #1                       ; R0 = flags, set pointer on (bit 0 = 1)

        LDR     R1, [WsPtr, #PointerShapeLA] ; last shape buffer given to HAL
        LDR     R4, [R3, #PointerBuffLA]     ; shape buffer we're about to give
        TEQ     R1, R4                       ; same as last time?
        STRNE   R4, [WsPtr, #PointerShapeLA] ; update
        ORRNE   R0, R0, #2                   ; flag new shape (bit 1 = 1)

        LDR     R1, [WsPtr, #PointerX]
        LDRB    R4, [WsPtr, #PointerXEigFactor]
        MOV     R1, R1, ASR R4                     ; R1 = pointer x, pixels
        LDRB    R4, [R3, #PointerActiveX]
        SUB     R1, R1, R4                         ; R1 = pointer x, adjusted for active point

        LDR     R2, [WsPtr, #PointerY]
        LDR     R4, [WsPtr, #DisplayYEigFactor]
        LDR     R5, [WsPtr, #DisplayYWindLimit]    ; R5 = display height -1
        SUB     R2, R5, R2, ASR R4                 ; R2 = pointer y, pixels, inverted
        LDRB    R4, [R3, #PointerActiveY]
        SUB     R2, R2, R4                         ; R2 = pointer y, adjusted for active point

        ; and its up to the HAL to handle clipping according to h/w capabilities
40
        LDR     R4, [WsPtr, #CurrentGraphicsVDriver]
        MOV     R4, R4, LSL #24
        ORR     R4, R4, #GraphicsV_UpdatePointer
        BL      CallGraphicsV

        ; Software pointer required?
        LDR     R5, [WsPtr, #GraphicsVFeatures]
        TST     R5, #GVDisplayFeature_HardwarePointer
        Pull    "pc", NE

        ; Software pointer code can run with IRQs enabled; drop into SVC mode
        MRS     R6, CPSR                     ; Currently we should always be in IRQ mode here, but read current mode just in case
        MSR     CPSR_c, #SVC32_mode
        MOV     R5, R14

        ; If the call wasn't claimed, pass on to software pointer code
        ; If the call was claimed, we need to make sure the software pointer is off
        TEQ     R4, #0
        MOVEQ   R0, #0
        BL      UpdateSoftwarePointer

        MOV     R14, R5
        MSR     CPSR_c, R6
        Pull    "pc"

        LTORG

; *****************************************************************************

SetMouseMult ROUT
        Push    "R11,R14"
        LDR     R11, =ZeroPage+KeyWorkSpace

      [ NoARMv4
        LDRB    R0, [R1, #1]
        MOV     R0, R0, ASL #24         ; sign extend to 32 bits
        MOV     R0, R0, ASR #24
      |
        LDRSB   R0, [R1, #1]
      ]
        STR     R0, MouseXMult

      [ NoARMv4
        LDRB    R0, [R1, #2]
        MOV     R0, R0, ASL #24         ; sign extend to 32 bits
        MOV     R0, R0, ASR #24
      |
        LDRSB   R0, [R1, #2]
      ]
        STR     R0, MouseYMult

        Pull    "R11,PC"

; *****************************************************************************
;
;       GetCoordPair - get pair of 2-byte coords from R1+1..R1+4
;       adds on graphics origin and sign extends to 32 bits
;       and puts X into R2, Y into R3
;

GetCoordPair ROUT
      [ NoARMv6 :LOR: NoUnaligned
        LDRB    R0, [R1, #1]            ; get X coordinate
        LDRB    R2, [R1, #2]
        ORR     R0, R0, R2, LSL #8
      |
        ; Use unaligned loads and SXTH from ARMv6
        LDRH    R0, [R1, #1]            ; get X coordinate
      ]

        LDR     R2, [WsPtr, #OrgX]      ; add on origin
        ADD     R0, R0, R2

      [ NoARMv6
        MOV     R0, R0, ASL #16         ; sign extend 16 to 32
        MOV     R2, R0, ASR #16
      |
        SXTH    R2, R0
      ]

      [ NoARMv6 :LOR: NoUnaligned
        LDRB    R0, [R1, #3]            ; get Y coordinate
        LDRB    R3, [R1, #4]
        ORR     R0, R0, R3, LSL #8
      |
        LDRH    R0, [R1, #3]            ; get Y coordinate
      ]

        LDR     R3, [WsPtr, #OrgY]      ; add on origin
        ADD     R0, R0, R3

      [ NoARMv6
        MOV     R0, R0, ASL #16         ; sign extend 16 to 32
        MOV     R3, R0, ASR #16
      |
        SXTH    R3, R0
      ]

        MOV     PC, R14

; *****************************************************************************

SetMousePosn ROUT
        Push    "R2, R3, R11, R14"
        LDR     R11, =ZeroPage+KeyWorkSpace

        BL      GetCoordPair

; now check point is within bounding box

        LDR     R0, MouseBoundLCol
        CMP     R2, R0
        LDRGE   R0, MouseBoundRCol
        CMPGE   R0, R2
        LDRGE   R0, MouseBoundBRow
        CMPGE   R3, R0
        LDRGE   R0, MouseBoundTRow
        CMPGE   R0, R3

        BLGE    SetMousePosnRegs

        Pull    "R2, R3, R11, PC"

SetMousePosnRegs
        LDR     R11, =ZeroPage+KeyWorkSpace
        STR     R2, MouseX
        STR     R3, MouseY
        B       FlushMouse

; *****************************************************************************
;
;       StoreCoordPair - Stores X,Y coords in R2,R3 in R1+1..R1+4
;       subtracts graphics origin

StoreCoordPair ROUT

        LDR     R0, [WsPtr, #OrgX]      ; subtract off origin
        SUB     R2, R2, R0

      [ NoARMv6 :LOR: NoUnaligned
        STRB    R2, [R1, #1]            ; store lo-byte of X
        MOV     R2, R2, LSR #8
        STRB    R2, [R1, #2]            ; store hi-byte of X
      |
        ; Use unaligned store from ARMv6
        STRH    R2, [R1, #1]            ; store X
      ]

        LDR     R0, [WsPtr, #OrgY]      ; subtract off origin
        SUB     R3, R3, R0

      [ NoARMv6 :LOR: NoUnaligned
        STRB    R3, [R1, #3]            ; store lo-byte of Y
        MOV     R3, R3, LSR #8
        STRB    R3, [R1, #4]            ; store hi-byte of Y
      |
        ; Use unaligned store from ARMv6
        STRH    R3, [R1, #3]            ; store X
      ]

        MOV     PC, R14

; *****************************************************************************

ReadMousePosn ROUT
        Push    "r0-r3, r9-r11, lr"
        BL      PollPointer             ; update mouse position on a read
        LDR     r1, [sp, #1*4]          ; reload pointer to buffer
        LDR     R11, =ZeroPage+KeyWorkSpace

        LDR     R2, MouseX              ; get mouse X
        LDR     R3, MouseY              ; get mouse Y
        BL      StoreCoordPair
        Pull    "r0-r3, r9-r11, pc"

; *****************************************************************************

SetPointerPosn ROUT
        Push    "R2, R3, R14"

        BL      GetCoordPair

        STR     R2, [WsPtr, #PointerX]
        STR     R3, [WsPtr, #PointerY]

        Pull    "R2, R3, PC"

; *****************************************************************************

ReadPointerPosn ROUT
        Push    "R2, R3, R14"

        LDR     R2, [WsPtr, #PointerX]
        LDR     R3, [WsPtr, #PointerY]
        BL      StoreCoordPair

        Pull    "R2, R3, PC"

; *****************************************************************************
;
;       FlushMouse - Flush mouse buffer
;
; out:  All registers preserved

FlushMouse ROUT
        Push    "R0-R2, R14"
        MOV     R0, #21
        MOV     R1, #Buff_Mouse
        SWI     XOS_Byte
        Pull    "R0-R2, PC"

        LTORG

; *****************************************************************************
;
;       RemovePointer - Remove soft mouse pointer from screen
;
; in:   WsPtr -> VduDriverWorkspace
; out:  flags preserved

RemovePointer ROUT
        EntryS  "r10-r11"
        LDRB    r10, [WsPtr, #SWP_Mutex]
        TEQ     r10, #0
        BNE     %FT90
        ; Lock mutex
        MOV     r10, #1
        STRB    r10, [WsPtr, #SWP_Mutex]
        ; We need to set SWP_Restore so that we know to release the mutex once we're done
        ; However if the software pointer currently isn't visible (hardware pointer in use) then there might not be an image to restore
        ; So SWP_Restore can take three values:
        ; 0 -> not in RemovePointer block
        ; 1 -> in RemovePointer but no restore needed
        ; 2 -> in RemovePointer and restore needed
        LDR     r11, [WsPtr, #SWP_Pos]
        TEQ     r11, #0
        MOVNE   r10, #2
        STRB    r10, [WsPtr, #SWP_Restore]
        ; Remove pointer if necessary
        LDRNE   r10, [WsPtr, #SWP_Under]
        BLNE    RemoveSoftwarePointer
        ; Exit with mutex still locked
90
        EXITS

; *****************************************************************************
;
;       RestorePointer - Restore soft mouse pointer to previous state
;
; in:   WsPtr -> VduDriverWorkspace
;       Software pointer restore assumed to be needed

RestorePointer ROUT
        STMFD   R13!,{R0-R7,R14}
        PHPSEI  R7                           ; IRQs off while we work out what to do
        LDRB    R1, [WsPtr, #SWP_Restore]
        MOV     R0, #0
        STRB    R0, [WsPtr, #SWP_Restore]
        STRB    R0, [WsPtr, #SWP_Mutex]
        LDRB    R6, [WsPtr, #PointerShapeNumber]
        TST     R1, #2                       ; Was the software pointer actually on?
        ANDNES  R6, R6, #&7F
        BEQ     %FT90

        ADD     R3, WsPtr, #PointerShapes-4
        LDR     R3, [R3, R6, LSL #2]         ; R3 -> current shape block (R6 = shape 1..4)

        MOV     R0, #1                       ; R0 = flags, set pointer on (bit 0 = 1)

        LDR     R1, [WsPtr, #PointerShapeLA] ; last shape buffer given to HAL
        LDR     R4, [R3, #PointerBuffLA]     ; shape buffer we're about to give
        TEQ     R1, R4                       ; same as last time?
        STRNE   R4, [WsPtr, #PointerShapeLA] ; update
        ORRNE   R0, R0, #2                   ; flag new shape (bit 1 = 1)

        LDR     R1, [WsPtr, #PointerX]
        LDRB    R4, [WsPtr, #PointerXEigFactor]
        MOV     R1, R1, ASR R4                     ; R1 = pointer x, pixels
        LDRB    R4, [R3, #PointerActiveX]
        SUB     R1, R1, R4                         ; R1 = pointer x, adjusted for active point

        LDR     R2, [WsPtr, #PointerY]
        LDR     R4, [WsPtr, #DisplayYEigFactor]
        LDR     R5, [WsPtr, #DisplayYWindLimit]    ; R5 = display height -1
        SUB     R2, R5, R2, ASR R4                 ; R2 = pointer y, pixels, inverted
        LDRB    R4, [R3, #PointerActiveY]
        SUB     R2, R2, R4                         ; R2 = pointer y, adjusted for active point

        ; The pointer may have moved while we had it disabled.
        ; If it has moved, it's possible the hardware pointer has taken over
        ; and the software pointer isn't needed anymore
        ; Potentially we could deal with this inside UpdateSoftwarePointer
        ; (i.e. cache the new parameters if it gets called with the mutex
        ; locked), but that will add a fair bit of complexity. So for now go
        ; with the simpler approach of comparing the current position against
        ; the last position used by the software pointer and calling through to
        ; GraphicsV if it's changed (otherwise, call software pointer directly)

      [ NoARMv6
        MOV     R5, R1, LSL #16
        MOV     LR, R2, LSL #16
        ORR     R5, LR, R5, LSR #16
      |
        PKHBT   R5, R1, R2, LSL #16
      ]
        LDR     LR, [WsPtr, #SWP_Coords]
        TEQ     LR, R5
        BEQ     %FT50

        ; Pointer has moved - call GraphicsV
        LDR     R4, [WsPtr, #CurrentGraphicsVDriver]
        MOV     R4, R4, LSL #24
        ORR     R4, R4, #GraphicsV_UpdatePointer
        BL      CallGraphicsV

        TEQ     R4, #0
        BEQ     %FT90

50
        ; IRQs on for software pointer call
        ; This does open up the possibility for the pointer shape/location to
        ; change under IRQ while we're still rendering it - so a more advanced
        ; locking mechanism may be desirable in future
        PLP     R7

        BL      UpdateSoftwarePointer
90
        PLP     R7
        LDMFD   R13!,{R0-R7,PC}

; *****************************************************************************
;
;       UpdateSoftwarePointer - Like GraphicsV_UpdatePointer, but for the
;                               software pointer
;
; in:   r0-r3 as per GraphicsV_UpdatePointer
;       WsPtr -> VduDriverWorkSpace

UpdateSoftwarePointer ROUT
        Entry   "r10-r11"
        ; Is the pointer possible?
        LDR     r10, [WsPtr, #SWP_Under]
        TEQ     r10, #0
        BEQ     %FT99
        ; Is mutex locked?
        LDRB    r11, [WsPtr, #SWP_Mutex]
        TEQ     r11, #0
        BNE     %FT99
        MOV     r11, #1
        STRB    r11, [WsPtr, #SWP_Mutex]
        ; Is pointer in same state as last time?
      [ NoARMv6
        MOV     r11, r1, LSL #16
        MOV     lr, r2, LSL #16
        ORR     r11, lr, r11, LSR #16
      |
        PKHBT   r11, r1, r2, LSL #16
      ]
        LDR     lr, [WsPtr, #SWP_Coords]
        TEQ     r11, lr ; Have coords changed?
        STRNE   r11, [WsPtr, #SWP_Coords]
        TSTEQ   r0, #2 ; Has shape changed?
        LDREQB  lr, [WsPtr, #SWP_Dirty]
        LDR     r11, [WsPtr, #SWP_Pos]
        TEQEQ   lr, #0 ; Has palette changed?
        BNE     %FT20
        ; The above state variables only track correctly for when the pointer is on. So we can only skip if it's currently on, and it's staying on.
        TEQ     r11, #0
        TSTNE   r0, #1
        BNE     %FT90
20
        ; Remove from previous position, if any
        TEQ     r11, #0
        BLNE    RemoveSoftwarePointer
        ; Plot in new position, if any
        TST     r0, #1
        BLNE    PlotSoftwarePointer
90
        ; Release mutex
        MOV     r11, #0
        STRB    r11, [WsPtr, #SWP_Mutex]
99
        EXIT

; *****************************************************************************
;
;       PlotSoftwarePointer - Plot software pointer to the screen
;
; in:   r1 = X position
;       r2 = Y position
;       r3 -> PointerBlkHAL
;       WsPtr -> VduDriverWorkSpace

PlotSoftwarePointer ROUT
        Entry   "r0-r11"
        ; Load shape info
        LDRB    r5, [r3, #PointerHeight]
        LDRB    r4, [r3, #PointerWidth]
        CMP     r5, #32
        LDR     r3, [r3, #PointerBuffLA]
        MOVGT   r5, #32
        ; Load screen info
        ; Must be careful to only use variables which aren't affected by output redirection
        LDR     r0, [WsPtr, #DisplayBankAddr]
        LDR     r6, [WsPtr, #DisplayXWindLimit]
        LDR     r7, [WsPtr, #DisplayYWindLimit]
        LDRB    r9, [WsPtr, #DisplayLog2BPP]
        ADD     r6, r6, #1
        ADD     r7, r7, #1
        MOV     r8, #0 ; Start X offset into pointer image (bits)
        MOV     r4, r4, LSL #2 ; byte width -> pixel width
        STRB    r8, [WsPtr, #SWP_Dirty]

        ; Do a quick scan of the image to see if the LHS is fully transparent
        ; This is the case for the default pointer image, at least
        CMP     r5, #0 ; Height can be zero on startup!
        ADD     r11, r3, r5, LSL #3
        BLE     %FT99
        MOV     r10, #0
10
        LDR     lr, [r11, #-8]!
        CMP     r11, r3
        ORR     r10, r10, lr
        BNE     %BT10
        TEQ     r10, #0
        ADDEQ   r8, r8, #32
        ADDEQ   r1, r1, #16
        SUBEQ   r4, r4, #16

        ; Crop image to screen
        CMP     r1, #0
        ADDLT   r4, r4, r1
        SUBLT   r8, r8, r1, LSL #1
        MOVLT   r1, #0
        ADD     lr, r1, r4
        SUBS    lr, lr, r6
        SUBGT   r4, r4, lr

        CMP     r2, #0
        ADDLT   r5, r5, r2
        SUBLT   r3, r3, r2, LSL #3
        MOVLT   r2, #0
        ADD     lr, r2, r5
        SUBS    lr, lr, r7
        SUBGT   r5, r5, lr

        ; Bail if fully off-screen
        CMP     r4, #0
        CMPGT   r5, #0
        BLE     %FT99

        LDR     r7, [WsPtr, #DisplayLineLength]
        MLA     r0, r7, r2, r0 ; First screen row to touch

        MOV     r1, r1, LSL r9 ; Screen X start, in bits

        ; Save the rectangle under the pointer
        ADR     lr, %FT30
        Push    "r0-r12,lr" ; For RemoveSoftwarePointerAltEntry
        ; Make things easier by converting to word-aligned coordinates
        ADD     lr, r1, r4, LSL r9 ; Screen X end, in bits
        ADD     lr, lr, #31
        MOV     r1, r1, LSR #5 ; X start in words
        RSB     r4, r1, lr, LSR #5 ; width in words

        ; Calculate screen addr to copy from
        ADD     r10, r0, r1, LSL #2 ; Copy dest

        ; Remember image size, position for restore later on
        STR     r10, [WsPtr, #SWP_Pos]
        STRB    r4, [WsPtr, #SWP_W]
        STRB    r5, [WsPtr, #SWP_H]

        ; Copy to SWP_Under
        ; Can reuse copy loop in RemoveSoftwarePointer
        SUB     r6, r7, r4, LSL #2 ; Src stride
        MOV     r7, #0 ; Dest stride
        LDR     r11, [WsPtr, #SWP_Under] ; Copy src
        B       RemoveSoftwarePointerAltEntry
30
        ; Arrive back here with r0-r12 restored
        ; Important values:
        ; r0 -> initial screen row
        ; r1 = screen X start, in bits
        ; r3 -> initial pointer row
        ; r4 = pointer pixel width
        ; r5 = height
        ; r7 = LineLength
        ; r8 = pointer X start, in bits
        ; r9 = Log2BPP
        ; For 32bpp modes we have a faster plotter available
        TEQ     r9, #5
        BEQ     %FT70
        ; Calculate end-of-row shift amount
        ADD     lr, r1, r4, LSL r9
        AND     lr, lr, #31
        RSB     lr, lr, #32
        ORR     lr, lr, lr, LSR #5 ; Use 33 to represent case where no shift is needed - ensures the MOVS clears C
        Push    "lr"
        ; Convert Log2BPP to BPP
        MOV     lr, #1
        MOV     r9, lr, LSL r9
        LDR     r10, =ZeroPage+VduDriverWorkSpace+SWP_Palette-4
        ; Conveniently, we can read screen words from SWP_Under, avoiding slow read-modify-write of screen memory
        LDR     r11, [WsPtr, #SWP_Under]
        ; Offset r0 to point at first word in row, compute initial mask word
        AND     lr, r1, #31
        ADD     r0, r0, r1, LSR #3
        MOV     r6, #&80000000
        BIC     r0, r0, #3 ; Row ptr assumed to be word aligned!
        MOV     r6, r6, LSR lr
        Push    "r0,r4,r6,r7,r8"
40
        ; Since the pointer is padded out to 32 pixels wide, it's easiest to
        ; just load an entire row and treat it as a 64 bit value
        LDMIA   r3!, {r1, r2}
        ; Skip the initial pixels
        TST     r8, #32
        AND     lr, r8, #31
        MOVNE   r1, r2
        RSB     r7, lr, #32
        MOV     r1, r1, LSR lr
        MVN     r8, #0
        ORR     r1, r1, r2, LSL r7
        MOV     r2, r2, LSR lr
        BIC     r8, r8, r8, LSR r9 ; Value to merge into mask word
        MOV     r7, #0
50
        ANDS    lr, r1, #3
        MOV     r1, r1, LSR #2
        LDRNE   lr, [r10, lr, LSL #2]
        ORR     r1, r1, r2, LSL #30
        ORR     r7, lr, r7, LSR r9 ; Merge in output pixel, 0 if transparent
        MOVNE   lr, r8
        MOV     r2, r2, LSR #2
        ORRS    r6, lr, r6, LSR r9 ; Merge in mask, 0 if transparent
        BCC     %FT60
        ; Store completed screen word
        ; EQ condition if fully transparent
        BEQ     %FT55
        LDR     lr, [r11]
        BIC     lr, lr, r6
        ORR     lr, lr, r7
        STR     lr, [r0]
55
        ADD     r11, r11, #4
        ADD     r0, r0, #4
        MOV     r6, #&80000000
60
        SUBS    r4, r4, #1
        BGT     %BT50
        ; Next row
        ; However, we may have a partial word to store
        ; Shift it down to the low end of the word, ready to store
        LDR     lr, [sp, #5*4] ; Grab precomputed shift amount
        MOVS    r6, r6, LSR lr
        MOV     r7, r7, LSR lr
        BLS     %FT65 ; ~C if no shift needed (actually, lr=33 to ensure C gets cleared), C+Z if word is transparent, C+~Z if visible
        LDR     lr, [r11]
        BIC     lr, lr, r6
        ORR     lr, lr, r7
        STR     lr, [r0]
65
        ADDCS   r11, r11, #4
        ; Last word dealt with
        ; Advance to next row
        LDMIA   sp, {r0,r4,r6,r7,r8}
        SUBS    r5, r5, #1
        ADD     r0, r0, r7
        STRNE   r0, [sp]
        BNE     %BT40
        ; Junk stack contents
        ADD     sp, sp, #6*4
99
        EXIT

70
        ; Plotter for 32bpp modes
        ; No need to read from the screen, just blast out any non-transparent
        ; pixels
        LDR     r10, =ZeroPage+VduDriverWorkSpace+SWP_Palette-4
        ; Offset r0 to point at first word in row
        ADD     r0, r0, r1, LSR #3
        ; Calculate shift amount for pointer row
        AND     r6, r8, #31
        RSB     r11, r6, #32
        Push    "r0,r4"
75
        ; Since the pointer is padded out to 32 pixels wide, it's easiest to
        ; just load an entire row and treat it as a 64 bit value
        LDMIA   r3!, {r1, r2}
        ; Skip the initial pixels
        TST     r8, #32
        MOVNE   r1, r2
        MOV     r1, r1, LSR r6
        ORR     r1, r1, r2, LSL r11
        MOV     r2, r2, LSR r6
80
        ANDS    lr, r1, #3
        MOV     r1, r1, LSR #2
        LDRNE   lr, [r10, lr, LSL #2]
        ORR     r1, r1, r2, LSL #30
        MOV     r2, r2, LSR #2
        STRNE   lr, [r0]
        SUBS    r4, r4, #1
        ORRNES  lr, r1, r2 ; Stop if remainder of row is transparent
        ADD     r0, r0, #4
        BNE     %BT80
        ; Next row
        LDMIA   sp, {r0,r4}
        SUBS    r5, r5, #1
        ADD     r0, r0, r7
        STRNE   r0, [sp]
        BNE     %BT75
        ; Junk stack contents
        ADD     sp, sp, #2*4
        EXIT



; *****************************************************************************
;
;       RemoveSoftwarePointer - Remove software pointer from the screen
;
; in:   r10 -> SWP_Under
;       r11 -> SWP_Pos
;       WsPtr -> VduDriverWorkSpace

RemoveSoftwarePointer
        Entry   "r0-r12" ; n.b. keep in sync with call from PlotSoftwarePointer
        ; Get parameters needed for unplot
        LDRB    r4, [WsPtr, #SWP_W]
        LDRB    r5, [WsPtr, #SWP_H]
        LDR     r6, [WsPtr, #DisplayLineLength]
        SUB     r7, r6, r4, LSL #2 ; Dest stride
        MOV     r6, #0 ; Src stride
        STR     r6, [WsPtr, #SWP_Pos]
RemoveSoftwarePointerAltEntry
        ; Width assumed to be max of 32 words
        ; r0-r3, r8-r9, r12, lr free for use
        Push    "r4"
05
        ; Width will almost always be >= 8, so transfer groups of 8 first
        CMP     r4, #8
06
        LDMGEIA r10!, {r0-r3, r8-r9, r12, lr}
        SUBGE   r4, r4, #8
        STMGEIA r11!, {r0-r3, r8-r9, r12, lr}
        BEQ     %FT90
        CMP     r4, #8
        BGE     %BT06
        ; Transfer remainder
        MOVS    r4, r4, LSR #1
        LDRCS   r0, [r10], #4
        STRCS   r0, [r11], #4
        BEQ     %FT90
        MOVS    r4, r4, LSR #1
        LDMCSIA r10!, {r0-r1}
        STMCSIA r11!, {r0-r1}
        BEQ     %FT90
        ; NE so must be 4 left
        LDMIA   r10!, {r0-r3}
        STMIA   r11!, {r0-r3}
90
        LDR     r4, [sp]
        ADD     r10, r10, r6
        SUBS    r5, r5, #1
        ADD     r11, r11, r7
        BNE     %BT05
        ADD     sp, sp, #4
        EXIT

; *****************************************************************************
;
;       RegisterSoftwarePointerCallback - Register callback for palette update
;
; in:   WsPtr -> VduDriverWorkSpace

RegisterSoftwarePointerCallback
        Entry   "r0-r2"
        ; We may be in IRQ mode, so switch to SVC before calling SWI
        MRS     r2, CPSR
        ORR     r1, r2, #SVC32_mode
        MSR     CPSR_c, r1
        Push    "lr"
        ADR     r0, SoftwarePointerCallback
        MOV     r1, WsPtr
        SWI     XOS_AddCallBack
        MOVVC   r0, #1
        STRVCB  r0, [WsPtr, #SWP_Callback]
        Pull    "lr"
        MSR     CPSR_c, r2
        EXIT

; *****************************************************************************
;
;       SoftwarePointerCallback - Recalculate software pointer palette
;
; in:   WsPtr -> VduDriverWorkSpace

SoftwarePointerCallback
        Entry   "r0-r3"
        MOV     r0, #0
        STRB    r0, [WsPtr, #SWP_Callback]
        ; Can now enable IRQs (ColourTrans may be slow!)
        MSR     CPSR_c, #SVC32_mode
        ; Grab the three pointer colours and translate them
        ; We want to shift the result up to the high end of each word
        LDRB    r0, [WsPtr, #DisplayLog2BPP]
        MOV     r1, #1
        MOV     r0, r1, LSL r0
        RSB     r3, r0, #32
        LDR     r2, [WsPtr, #FirPalAddr]
        LDR     r1, [WsPtr, #DisplayModeNo]
        LDR     r0, [r2, #257*4]
        SWI     XColourTrans_ReturnColourNumberForMode
        MOV     r0, r0, LSL r3
        STR     r0, [WsPtr, #SWP_Palette]
        LDR     r0, [r2, #258*4]
        SWI     XColourTrans_ReturnColourNumberForMode
        MOV     r0, r0, LSL r3
        STR     r0, [WsPtr, #SWP_Palette+4]
        LDR     r0, [r2, #259*4]
        SWI     XColourTrans_ReturnColourNumberForMode
        MOV     r0, r0, LSL r3
        STR     r0, [WsPtr, #SWP_Palette+8]
        STRB    r1, [WsPtr, #SWP_Dirty]
        EXIT

        LTORG

        END