; 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.VduDriver
;
; ARTHUR OPERATING SYSTEM - Vdu Drivers
; =======================
;
; Vdu driver code - Vdu queue, mode, default windows etc.
;
; Author R C Manby
; Date   5.9.86
;

        GET s.vdu.VduGrafDec

        MACRO
        RMVT    $var, $BW
        [ "$BW"="B"
        =       (wk$var-wkstart)*2
        |
        [ "$BW"="W"
        =       (wk$var-wkstart)*2 +1
        |
        .... Invalid option on RMVT ....
        ]
        ]
        MEND

        MACRO
        RVVT    $var
        ASSERT  ($var >= 0) :LAND: ($var < &400) :LAND: (($var :AND: 3)=0)
        =       $var :SHR: 2
        MEND

        MACRO
        IssueService
        BL      Issue_Service
        MEND

        MACRO
        MALIGN  $length, $phase
        LCLA    temp
        LCLS    string
temp    SETA    .-ArthurVduDriver
        [ "$phase"=""
string  SETS    "ALIGN $length"
        |
string  SETS    "ALIGN $length, $phase"
        ]
        $string
temp    SETA    .-ArthurVduDriver-temp
        [ temp=0
        !       0, string :CC: " fitted exactly"
        |
        !       0, string :CC: " wasted " :CC: :STR: temp
        ]
        MEND


; Macro to load up video bandwidth and video memory size

        MACRO
        GetBandwidthAndSize  $bw, $size
        LDR     $size, =ZeroPage
        LDR     $size, [$size, #VideoSizeFlags]
        MOV     $bw, #100*1024*1024
        MOV     $size, $size, LSR #12
        MOV     $size, $size, LSL #12
        !       0, "Sort out GetBandwidthAndSize"
        MEND

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

; Vdu status bits

Vdu2ModeBitPosn *       0
Vdu2Mode        *       1 :SHL: Vdu2ModeBitPosn
Windowing       *       1 :SHL: 3
Shadowing       *       1 :SHL: 4

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

ArthurVduDriver

; *****************************************************************************
;
; VduInit - Once only initialisation of Vdu drivers eg after Break
; =======
;
VduInit ROUT
        Push    R14
        LDR     R0, =ZeroPage
        LDR     R14, [R0, #VideoPhysAddr]
        ASSERT (ZeroPage :AND: 255) = 0
        STRB    R0, [R0, #OsbyteVars + :INDEX: VDUqueueItems] ;purge queue
        STRB    R0, [WsPtr, #ScreenBlankFlag]   ; not blanked
        MOV     R0, #255
        STRB    R0, [WsPtr, #ScreenBlankDPMSState] ; however, no mode programmed yet
        MOV     R0, #0
        STR     R0, [WsPtr, #CursorCounter]
        STR     R0, [WsPtr, #CursorDesiredState]
        STR     R0, [WsPtr, #VduStatus]
        STRB    R0, [WsPtr, #PointerShapeNumber]  ; make sure pointer off
        STR     R0, [WsPtr, #PointerShapeLA]      ; no shape passed to HAL yet
        STR     R0, [WsPtr, #CursorStack]         ; 0 bits => on
        STR     R0, [WsPtr, #VduSaveAreaPtr]      ; indicate no save area yet
        STR     R0, [WsPtr, #ClipBoxEnable]       ; no clip box calculating
        STRB    R0, [WsPtr, #ExternalFramestore]
        STR     R0, [WsPtr, #GraphicsVFeatures]
        MOV     R3, #MaxGraphicsVDrivers
        LDR     R2, =ZeroPage+VduDriverWorkSpace+GraphicsVDrivers
01
        STR     R0, [R2], #4
        SUBS    R3, R3, #1
        BNE     %BT01
        MOV     R0, #GraphicsVInvalid
        STR     R0, [WsPtr, #CurrentGraphicsVDriver]
        STR     R14, [WsPtr, #TrueVideoPhysAddr]  ; init TrueVideoPhysAddr for internal RAM/VRAM framestore case

        LDR     R0, =RangeC+SpriteReason_SwitchOutputToSprite
        STR     R0, [WsPtr, #SpriteMaskSelect]

        MOV     R0, #InitialCursorFlags         ; TMD 25/9/86
        STR     R0, [WsPtr, #CursorFlags]

        ADRL    R0, NUL                 ; point to MOV PC,R14
        STR     R0, [WsPtr, #WrchNbit]  ; just in case ...

        ADRL    R0, ExportedHLine
        STR     R0, [WsPtr, #HLineAddr]
        ADD     R0, WsPtr, #FgEcfOraEor
        STR     R0, [WsPtr, #GcolOraEorAddr]

        MOV     R0, #maxmode
        STR     R0, [WsPtr, #MaxMode]   ; constant now


        LDROSB  R0, LastBREAK           ; is it a hard reset ?
        TEQ     R0, #SoftReset
        BEQ     %FT10                   ; [no, don't reset font]


; allocate buffer for Tim's whizzy text expansion.  This now lives
; in the system heap, unless the claim request fails.  If the request
; fails then the pointer points at the original buffer in the Vdu
; driver workspace.

; This allows the user to access depths upto 16 bit per pixel before
; strings things things become afoot at the Circle K.

        LDR     R3, =TextExpandArea_Size
        BL      ClaimSysHeapNode        ; allocate buffer for heap data
        ADDVS   R2, WsPtr, #TextExpand
        STR     R2, [WsPtr, #TextExpandArea]

        LDR     R3, =ModeSelector_MaxSize+4     ; get block for two overlapping copies
        BL      ClaimSysHeapNode                ; of mode selector, 1 word apart
        STR     R2, [WsPtr, #KernelModeSelector]


; now reset soft font from hard font

        MOV     R1, #1                  ; start at character 1*32
        STRB    R1, [WsPtr, #ScreenMemoryClaimed]
                                        ; can't claim memory till a mode change
        MOV     R2, #7                  ; copy 7 pages
        BL      DoResetFont

;initialise the 6 pointer shape pointers and blocks
;(shape buffers are 6 * &100 starting at CursorData)
;
        ADD     r0, WsPtr, #PointerShapes
        LDR     r2, =CursorData
        MOV     r3, #6
        ADD     r4, WsPtr, #PointerShapeBlocks
02
        STR     r4, [r0]                 ; attach pointer to block
        STR     r2, [r4, #PointerBuffLA]
        Push    "r0-r2"
        MOV     r1, #0
        STRB    r1, [r4, #PointerWidth]  ; zero width
        STRB    r1, [r4, #PointerHeight] ; zero height (no shape)
        SUB     sp, sp, #3*4             ; room for one entry of OS_Memory page block
        MOV     r1, sp
        STR     r2, [r1, #4]             ; provide logical address
        MOV     r2, #1
        MOV     r0, #&2200               ; convert logical to physical address
        SWI     XOS_Memory
        LDR     r2, [r1, #8]             ; read physical address
        STR     r2, [r4, #PointerBuffPA]
        ADD     sp, sp, #3*4
        Pull    "r0-r2"
        ADD     r4, r4, #PointerBlkSize
        ADD     r2, r2, #&100
        ADD     r0, r0, #4
        SUBS    r3, r3, #1
        BNE     %BT02

; palette space (256 normal + 1 border + 3 pointer = 260), and allowing for Gamma Correction
;   this space is: blank palette, 260 words
;                  logical and physical copies of both flash states, 260*4 words
;                  3 lookup tables for r,g,b mapping, 3*256 bytes
;
        LDR     r3, =Pal_Blocksize
        BL      ClaimSysHeapNode           ; this had better succeed!

        ASSERT  :INDEX:Pal_Blank = 0
        STR     r2, [WsPtr, #BlankPalAddr]
        ADD     r3, r2, #Pal_LogFirst
        STR     r3, [WsPtr, #FirPalAddr]
        ADD     r3, r2, #Pal_LogSecond
        STR     r3, [WsPtr, #SecPalAddr]

; initialise blank palette to all solid black
; zero-init the other palettes too, to make sure log & phys palettes are
; consistent

        MOV     r3, #0
        LDR     r4, =PalEntries*5
04      STR     r3, [r2], #4
        SUBS    r4, r4, #1
        BNE     %BT04

        ASSERT  Pal_RTable = Pal_Blank + PalEntries*5*4

; initialise red, green and blue transfer function tables to 1-1 mapping

        MOV     r0, #0
05
        STRB    r0, [r2, #Pal_STable-Pal_RTable] ; store in supremacy table
        STRB    r0, [r2, #Pal_BTable-Pal_RTable] ; store in blue table
        STRB    r0, [r2, #Pal_GTable-Pal_RTable] ; store in green table
        STRB    r0, [r2], #1                     ; store in red table, and advance
        ADD     r0, r0, #1
        CMP     r0, #256
        BCC     %BT05
10
        BL      SpriteInit

        LDR     R14, [WsPtr, #ScreenEndAddr]
        LDR     R0, [WsPtr, #TotalScreenSize]
        RSB     R0, R0, R14
        STR     R0, [WsPtr, #DisplayStart]
        BL      SetDisplayScreenStart
        STR     R0, [WsPtr, #ScreenStart]
        STR     R0, [WsPtr, #CursorAddr]
        STR     R0, [WsPtr, #InputCursorAddr]

        Pull    PC

        LTORG

; *****************************************************************************
;
;       InitialiseMode - Select mode number given by ModeNo variable
;
;       Called by MOS once before initialisation, and then afterwards
;       before printing "RISC OS ..."
;
; in:   -
; out:  r0 = corrupt, or error pointer
;       All other registers preserved
;

InitialiseMode ROUT
; if we don't have a video driver yet, now is a good time to check if the HAL
; provides one
        LDR     r0, =ZeroPage+VduDriverWorkSpace+CurrentGraphicsVDriver
        LDR     r0, [r0]
        CMP     r0, #GraphicsVInvalid
        BEQ     VduGrafHAL_Init         ; tail-call since InitialiseMode will get called again when the HAL registers itself

        Entry   "r1-r12"

        ; Refresh cached features flags before we start calling this new driver
        MOV     r4, r0, LSL #24
        ORR     R4, R4, #GraphicsV_DisplayFeatures
        BL      CallGraphicsV
        VDWS    WsPtr
        STR     r0, [WsPtr, #GraphicsVFeatures]

        ; Update screen memory information
        ASSERT  GVDisplayFeature_SeparateFramestore < 256
        ANDS    r1, r0, #GVDisplayFeature_SeparateFramestore
        STRB    r1, [WsPtr, #ExternalFramestore]
        BNE     %FT20
        ; Screen DA is in use
        LDR     r0, =ZeroPage
        LDR     r0, [r0, #VideoPhysAddr]
        STR     r0, [WsPtr, #TrueVideoPhysAddr] ; Point TrueVideoPhysAddr at the base of screen DA
        MOV     r0, #2
        SWI     XOS_ReadDynamicArea
        MOVVS   r1, #0                  ; shouldn't happen, but set a safe size just in case
        STR     r1, [WsPtr, #TotalScreenSize] ; Ensure TotalScreenSize consistent with DA size
        ; Reinitialise a few more variables which are used by the screen DA handler
        ADD     r1, r0, r1
        STR     r1, [WsPtr, #ScreenEndAddr]
        STR     r0, [WsPtr, #DisplayStart]
        BL      SetDisplayScreenStart
        STR     r0, [WsPtr, #ScreenStart]
        B       %FT30
20
        ; Driver manages memory itself
        TST     r0, #GVDisplayFeature_VariableFramestore
        BNE     %FT30                   ; Framestore changes with mode, we can't read its info here
        ; Framestore is fixed, get its info and remember it (ModeChangeSub currently relies on this for VRAM limit checking)
        LDR     r0, =ZeroPage+VduDriverWorkSpace+CurrentGraphicsVDriver
        LDR     r0, [r0]
        MOV     r4, r0, LSL #24
        ORR     r4, r4, #GraphicsV_FramestoreAddress
        BL      CallGraphicsV
        CMP     r4, #0
        MOVNE   r1, #0                  ; If call wasn't claimed, claim 0 screen memory. Mode change will then fail with out of memory error, which is about the best we can do.
        STR     r0, [WsPtr, #TrueVideoPhysAddr]
        STR     r1, [WsPtr, #TotalScreenSize]

30

        MOV     r0, #1                  ; no need to check for soft reset,
        SWI     XOS_ReadSysInfo         ; always use configured value
        MOV     r1, r0

        MOV     r0, #ScreenModeReason_SelectMode
        SWI     XOS_ScreenMode
        BVC     %FT40

        MOV     r0, #114                ; failed, so get rid of any shadow
        MOV     r1, #1
        SWI     XOS_Byte
        SWI     XOS_WriteI+22
        SWIVC   XOS_WriteI+0            ; and if we can't get mode 0, we're really fooked!!!
40
        BLVC    UpdateAllPalette        ; make sure GV driver is aware of the pointer colours (this will also redundantly set the main palette + border colour, but re-using this routine as-is avoids duplicating a bunch of code)
        EXIT

;
;
;------------------------------------------------------------------------------
;
; Vdu -  Main VDU driver entry point
; ===    Queue up bytes and dispatch via JVec
;
; in:   R0 = character to be printed
;
; out:  C=1 <=> send character to printer if enabled
;

Vdu     ROUT
        LDR     R11, =ZeroPage          ; NB used later in Vdu07 as well
        LDRB    R10, [R11, #OsbyteVars + :INDEX: VDUqueueItems]
        MOVS    R9, R10, LSL #24        ; move up to top byte (for sign extend)
        BEQ     Vdu07                   ; not queueing, then start Vdu sequence

; *****Comment made by DJS: Changing this to fall through if not queueing
; and branch if queueing might be a good idea - it would speed up all
; printable characters and simple cursor movements.

        LDR     R8, [WsPtr, #QOffset]
        STRB    R0, [R8, R9, ASR #24]   ; add byte to queue

        ADD     R10, R10, #1            ; move on pointer
        STRB    R10, [R11, #OsbyteVars + :INDEX: VDUqueueItems]

        CMP     R10, #&100              ; finished sequence ?
        MOVCC   PC, R14                 ; no, then return (no printing)

        Push    "R0, R14"
        BL      PreWrchCursor           ; exits with R6 = CursorFlags
        Pull    "R0"
05
        ADR     R14, VduPrintExit
        Push    R14
        BL      Vdu05
        BL      PostWrchCursor
        CLC                             ; also clears V
        ADD     SP,SP,#4
        Pull    PC

VduPrintExit                             ; exit used if print required
        BL      PostWrchCursor
        SEC                             ; also clears V
        Pull    PC

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

Vdu05   ROUT
        TST     R6, #VduDisabled        ; VDU enabled ?
        LDREQ   PC, [WsPtr, #JVec]      ; yes, then do it (no printing)

; Queue sequence has just completed, but VDU is disabled
; Only interesting case is VDU 1 (SOH), which must still print
; the character if the printer is enabled

        LDR     R10, [WsPtr, #JVec]
        ADR     R11, SOH
        TEQ     R10, R11                ; JVec -> SOH ?
        MOVNE   PC, R14                 ; no, then return (no printing)
SOH
        LDR     R1, [WsPtr, #VduStatus]  ; in VDU 2 mode ?
        TST     R1, #Vdu2Mode
        MOVEQ   PC, R14                 ; no, then return

        Push    R14
        BL      MOSDoPrint
        Pull    PC, VC                  ; good exit, so return

        Pull    R14                     ; bad exit, return with error
        B       VduBadExit

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

Vdu07   ROUT
        AND     R0, R0, #&FF            ; Only look at bottom byte!

        CMP     R0,#127                 ;If DELETE, change R0 to 32 and
        MOVEQ   R0,#32                  ;  drop through to control char code
        CMPNE   R0,#31                  ;Otherwise, branch to printable char
        BHI     Vdu20                   ;  code if not a control char

; *****Further desirable change: Make printable characters fall through,
; control characters take the branch - speeding up printable characters is
; important!

        ADR     R10, VduJTb             ; Address of beginning of jump table
        LDR     R9, [R10, R0, LSL #2]   ; Get routine address from VduJTB
        STR     R9, [WsPtr, #JVec]      ; save address

        ADD     R10, R10, #(VduQTb-VduJTb)

        LDRB    R9, [R10, R0]           ; Pick up QQ + length of queue
        RSBS    R10, R9, #QQ            ; -(length of queue) & test if none

        STRNEB  R10, [R11, #OsbyteVars + :INDEX: VDUqueueItems]
                                        ; yes, then set up Osbyte variable
        ADDNE   R9, R9, WsPtr
        STRNE   R9, [WsPtr, #QOffset]   ; and QOffset
        MOVNE   PC, R14

        Push    "R0,R14"
        BL      PreWrchCursor           ; exits with R6 = CursorFlags
        Pull    "R0"
10
        ADR     R14, VduPrintExit
        Push    R14
        BL      Vdu10
        BL      PostWrchCursor
        CLC                             ; also clears V
        ADD     SP, SP, #4
        Pull    PC

;
; This is the only byte of a single byte Vdu sequence
;
;       R6 = CursorFlags
;       R11 = 0 (used to index VDUqueueItems)
;

Vdu10   ROUT
        TST     R6, #CursorsSplit       ; are we cursor editing ?
        BNE     Vdu15
Vdu10Continue

; TMD 31/7/87; bug fixed here - chars 8 to 13 should still go to printer if
; enabled, even if VDU is disabled

        CMP     R0, #8                  ; is char in range 8-13 ?
        RSBCSS  R1, R0, #13             ; if so then we want to print it
        LDRCS   R1, [WsPtr, #VduStatus]
        MOVCSS  R1, R1, LSR #(Vdu2ModeBitPosn +1) ; providing printer enabled
        Pull    R14, CS                 ; so pull old R14

        TST     R6, #VduDisabled        ; are we disabled
        LDREQ   PC, [WsPtr, #JVec]      ; enabled, so go get em floyd !

        TEQ     R0, #6                  ; disabled, so is it ACK (enable VDU)
        BICEQ   R6, R6, #VduDisabled
        STREQ   R6, [WsPtr, #CursorFlags]

NUL ;Does nothing
ESC ;Does nothing
        MOV     PC, R14                 ; return anyway

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

Vdu15
        TEQ     R0, #13                 ; and is it a carriage return ?
        BNE     Vdu10Continue

        Push    "R0, R14"
        BIC     R6, R6, #CursorsSplit   ; then stop cursor editing
        STR     R6, [WsPtr, #CursorFlags]
        MOV     R1, #1                  ; restore old Reg10Copy
        BL      CursorOnOff
        Pull    "R0, R14"
        B       Vdu10Continue

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

Vdu20   ROUT
        Push    "R0,R14"
        BL      PreWrchCursor           ; exits with R6 = CursorFlags
        Pull    "R0"
05
        ADR     R14, VduPrintExit
        Push    R14
        BL      TimWrch
        BL      PostWrchCursor
        CLC                             ; also clears V
        ADD     SP, SP, #4
        Pull    PC

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

VduJTb                  ; Table of addresses
 & NUL                          ; Does nothing
 & SOH                          ; Next char to printer only
 & STX                          ; Enable printer
 & ETX                          ; Disable printer
 & EOT                          ; Write text at text cursor
 & ENQ                          ; Write text at graphics cursor
 & NUL                          ; Enable VDU drivers
 & BEL                          ; Beep
 & BS                           ; Cursor left
 & HT                           ; Cursor right
 & VduLF                        ; Cursor down
 & VT                           ; Cursor up
 & FF                           ; Clear text area (CLS)
 & VduCR                        ; Carriage return
 & SO                           ; Page mode on
 & SI                           ; Page mode off
 & DLE                          ; Clear graphics area (CLG)
 & DC1                          ; Define text colour (COLOUR)
 & DC2                          ; Define graphics colour and action (GCOL)
 & DC3                          ; Define logical colour
 & VDU20                        ; Restore default logical colours
 & NAK                          ; Disable VDU drivers
 & SYN                          ; Select screen mode (MODE)
 & ETB                          ; Reprogram display character (VDU23,...)
 & CAN                          ; Define graphics window
 & EM                           ; (PLOT k,x,x,y,y)
 & DefaultWindows               ; Restore default windows
 & ESC                          ; Does nothing
 & FS                           ; Define text window
 & GS                           ; Define graphics origin
 & RS                           ; Home cursor to "top left"
 & US                           ; Move text cursor (TAB x,y)
 & Delete                       ; Delete character (127)

        ASSERT  QQ+9 < 256

VduQTb          ; QQ + length of queue for each of the above
        DCB     QQ+0, QQ+1, QQ+0, QQ+0, QQ+0, QQ+0, QQ+0, QQ+0
        DCB     QQ+0, QQ+0, QQ+0, QQ+0, QQ+0, QQ+0, QQ+0, QQ+0
        DCB     QQ+0, QQ+1, QQ+2, QQ+5, QQ+0, QQ+0, QQ+1, QQ+9
        DCB     QQ+8, QQ+5, QQ+0, QQ+0, QQ+4, QQ+4, QQ+0, QQ+2
        DCB     QQ+0 ; (delete)
        ALIGN

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

WrchNbitTab
        &       Wrch1bit-WrchNbitTab
        &       Wrch2bit-WrchNbitTab
        &       Wrch4bit-WrchNbitTab
        &       Wrch8bit-WrchNbitTab
        &       Wrch16bit-WrchNbitTab
        &       Wrch32bit-WrchNbitTab
WrchNbitDoubleTab
        &       Wrch1bitDouble-WrchNbitDoubleTab

CursorNbitTab
        &       Cursor1bit-CursorNbitTab
        &       Cursor2bit-CursorNbitTab
        &       Cursor4bit-CursorNbitTab
        &       Cursor8bit-CursorNbitTab
        &       Cursor16bit-CursorNbitTab
        &       Cursor32bit-CursorNbitTab
        &       Cursor64bit-CursorNbitTab


; *****************************************************************************
;
;       SYN - Perform MODE change
;
;       External routine
;
; in:   Vdu queue contains mode number
;

SYN     ROUT                            ; Select screen mode (MODE)
        Push    lr
        LDRB    R2, [WsPtr, #QQ]        ; Read mode number from queue
        BL      ModeChangeSub
        Pull    lr
        MOVVC   pc, lr                  ; if no error, exit to Vdu
                                        ; (which calls PostWrchCursor)
; else drop thru into ...

VduBadExit                              ; jumped to if an error in VDU code
        Push    R0                      ; save error pointer
        BL      PostWrchCursor
        LDR     R0, [R13], #8           ; get error pointer, junk next entry
        SETV
        Pull    PC

; in: R2 = mode number / selector block
ModeChangeSub ROUT
        ; Wrap the bulk of ModeChangeSub in some code that will free any new
        ; TTX workspace pointer if the mode change fails (or the pointer isn't
        ; claimed for any other reason)
        Entry
        MOV     r0, #0
        STR     r0, [WsPtr, #TTXNewWorkspace]
        BL      %FT01
        LDR     r2, [WsPtr, #TTXNewWorkspace]
        TEQ     r2, #0
        EXIT    EQ
        MRS     r3, CPSR
        MOV     r4, r0
        BL      FreeSysHeapNode
        MSR     CPSR_c, r3
        MOV     r0, r4
        EXIT

01
        Push    lr

        MOV     R1, #Service_PreModeChange
        IssueService
        TEQ     R1, #0                  ; was service claimed ?
        BNE     %FT03                   ; no, so continue
        CMP     R0, #0                  ; service claimed; generate error ?
        Pull    PC, EQ                  ; no, just exit (V=0 from CMP)
        B       %FT07                   ; yes, then generate error
03
        MOV     R0, R2                  ; put mode (possibly changed) in R0
        MOV     R2, R0, LSR #7          ; R2 = 0/1 if bit 7 of mode clear/set
        CMP     r2, #2                  ; if mode number >= 256 then mode selector
        MOVCS   r2, #0                  ; so no shadow
        LDROSB  R1, Shadow
        TEQ     R1, #0                  ; if shadow 0 then force shadow mode
        MOVEQ   R2, #1

        BL      FindOKMode              ; out: R1 = mode we are going to use
        BVS     %FT07

        TEQ     R0, R1                  ; if substitute mode
        MOVNE   R2, #0                  ; then don't use shadow

        CMP     r1, #&100
        BICCC   r10, r1, #&80
        MOVCS   r10, r1
        MOV     R11, R10
        BL      PushModeInfo
        BVS     %FT07                   ; [probably duff mode selector]

        ; Allocate new teletext workspace if required
        LDR     R0, [R13, #wkModeFlags]
        TST     R0, #ModeFlag_Teletext
        MOVNE   R0, R13
        BLNE    TeletextAlloc
        BVS     %FT07

        Push    R1                      ; save proper mode

        ; Vet the mode - both to make sure the driver really is happy with it,
        ; and to cope with drivers which want to switch to different framebuffer
        ; types
        ADD     R0, R13, #wkwordsize+4
        Push    "R2"
        BL      DoFullVetMode
        Pull    "R2"
        SUB     R13, R13, #12
        BEQ     %FT041                  ; not supported, complain
        TST     R0, #GVVetMode2_ExtraBytes_Invalid
        BNE     %FT041                  ; Service_ModeExtension should have made sure ExtraBytes was valid
        ; R0, R3, R5 may contain important info
        STMIA   R13, {R0, R3, R5}
        AND     R0, R0, #GVVetMode2_ResultMask
        CMP     R0, #GVVetMode2_Result_UnkFramestore
        BEQ     %FT08                   ; Driver doesn't know where mode is going to be, so we can't vet the memory requirement

        LDR     R11, [R13, #16 + wkScreenSize] ; get screen size for this mode
        MOV     R11, R11, LSL R2        ; total amount needed

        CMP     R0, #GVVetMode2_Result_SysFramestore
        BNE     %FT045

        MOV     R0, #2
        SWI     XOS_ReadDynamicArea
        SUBS    R1, R11, R1
        BLE     %FT08

; try to extend the amount of screen memory

        ! 0, "Need to fix ModeChangeSub to not leave CursorAddr, etc. pointing to unmapped pages during DA resize. Causes bad stuff should an abort occur/screen output be attempted!" ; Note that even enlarging the DA can leave the pointers in a bad state, due to the way the screen DA handler shuffles down/unmaps the lower mapping of VRAM before the higher copy is enlarged

        MOV     R0, #2                  ; expand screen memory
        SWI     XOS_ChangeDynamicArea
        BVC     %FT08
06
        ADR     R0, ErrorBlock_BadMODE
065
        ADD     R13, R13, #PushedInfoSize + 4*4 ; junk stacked info + mode no + vetmode2 info
      [ International
        BL      TranslateError
      ]
07
        SETV                            ; indicate error
        Pull    PC

041
        ADRL    R0, ErrorBlock_ModeNotAvailable
        B       %BT065

045
        CMP     R11, R5
        BHI     %BT06                   ; ext. framestore not big enough

; valid mode and enough memory

08
        LDR     r0, [sp, #12]          ; restore mode we are using
        CMP     r0, #&100               ; if not mode selector
        BICCC   r0, r0, #&80            ; then knock off shadow bit
        BCC     %FT12

; it's a mode selector, so copy it to our static mode selector block

        LDR     r1, [WsPtr, #KernelModeSelector] ; point at block

        SUBS    r3, r0, r1              ; if r0 -> 1st mode block position
        TEQNE   r3, #4                  ; or r0 -> 2nd mode block position
        MOVEQ   r1, r0                  ; then use it in place
        BEQ     %FT09

        LDR     r3, [WsPtr, #DisplayModeNo] ; else check if current mode is a mode selector
        SUB     r3, r3, r1              ; r3 = offset from start of block
        CMP     r3, #8                  ; if 0 or 4
        EORCC   r3, r3, #4              ; then make 4 or 0 (ie toggle between them)
        ADDCC   r1, r1, r3              ; and add on base

        ASSERT  (ModeSelector_ModeVars+4) :AND: 7 = 0
09
        MOV     r3, #0
10
        LDR     r6, [r0, r3]            ; copy 1st word - after fixed bit this will be previous var value
        STR     r6, [r1, r3]
        ADD     r3, r3, #4
        LDR     r6, [r0, r3]            ; copy 2nd word - after fixed bit this will be next var index
        STR     r6, [r1, r3]
        ADD     r3, r3, #4
        CMP     r3, #ModeSelector_ModeVars + 4  ; only exit if we've done the essential bit
        CMPCS   r6, #-1                 ; AND we've had a -1 as the var index (NOT as the value)
        CMPCC   r3, #ModeSelector_MaxSize ; OR we've gone off the end of our block
        BCC     %BT10                   ; [we haven't, so loop]

        CMP     r3, #ModeSelector_MaxSize       ; if we did go off the end
        MOVCS   r6, #-1
        STRCS   r6, [r1, #ModeSelector_MaxSize-4] ; then terminate it properly

        MOV     r0, r1                  ; point at static block
12
        STR     R0, [WsPtr, #DisplayModeNo] ; store the new display mode

; now issue Service_ModeChanging

        MOV     R1, #Service_ModeChanging
        BL      IssueModeService

; R13 -> mode variables

13      LDR     R3, [R13, #16+wkScreenSize]
        STR     R3, [WsPtr, #ScreenSize] ; store screensize BEFORE calling
                                        ; ConvertBankToAddress (was a bug!)

        Push    "r0-r6"

; If driver handles memory allocation, must change mode before asking for memory
; (the case where we change mode after setting up the memory is only retained for backwards-compatibility, just in case something special like Aemulor is relying on it)

        LDR     r0, [sp, #7*4]
        AND     r0, r0, #GVVetMode2_ResultMask
        CMP     r0, #GVVetMode2_Result_SysFramestore
        BEQ     %FT581
        CMP     r0, #GVVetMode2_Result_ExtFramestore
        LDREQ   r0, [sp, #8*4]
        LDREQ   r1, [sp, #9*4]
        BEQ     %FT580
        ; Must be UnkFramestore
        ADD     R0, R13, #wkwordsize+7*4+16     ; R0 -> VIDCList3
        BL      HardwareModeChange
        LDR     r4, [WsPtr, #CurrentGraphicsVDriver]
        MOV     r4, r4, LSL #24
        ORR     r4, r4, #GraphicsV_FramestoreAddress
        BL      CallGraphicsV

580
; for mapping in, round start address down and size up to megabyte boundaries
; r0 = physical start, r1= size
; so frame buffer is wholly contained within the mapped in area
        MOV     r3, #1<<20                      ; 1 Megabyte
        SUB     r3, r3, #1                      ; convert to mask
        MOV     r4, r1                          ; remember what was asked for
        AND     r5, r0, r3                      ; and offset from megabyte base
        TST     r1, r3                          ; non integer megabyte?
        BIC     r2, r1, r3                      ; (clear the bits)
        ADDNE   r2, r2, #1<<20                  ; yes.. up to next megabyte
        BIC     r1, r0, r3                      ; ensure megabyte boundary at start
        MOV     r0, #OSMemReason_MapIOPermanent ; map in permanently
        ORR     r0, r0, #1:SHL:8                ; buffered, uncached
        ORR     r0, r0, #1:SHL:17               ; access permission specified (= usermode access)
        LDR     lr, [WsPtr, #GraphicsVFeatures]
        TST     lr, #GVDisplayFeature_HardwareScroll
        ORRNE   r0, r0, #1:SHL:16               ; doubly map for hardware scrolling (n.b. assuming VRAM is megabtye aligned)
        SWI     XOS_Memory
        BVS     %FT581
        ADD     r0, r1, r5                      ; reconstruct base phys address
        STR     r0, [WsPtr, #TrueVideoPhysAddr] ; and update our copy
        STR     r4, [WsPtr, #TotalScreenSize]   ; what we asked for
        ADD     r3, r3, r4                      ; compute end
        ADD     r3, r3, r5                      ; and allow offset dfrom start
        STR     r3, [WsPtr, #ScreenEndAddr]     ; actual screen end
        MOV     r14, #1
        B       %FT582

581
        LDR     r0, =ZeroPage
        LDR     r0, [r0, #VideoPhysAddr]
        STR     r0, [WsPtr, #TrueVideoPhysAddr] ; Point TrueVideoPhysAddr at the base of screen DA
        MOV     r0, #2
        SWI     XOS_ReadDynamicArea
        STRVC   r1, [WsPtr, #TotalScreenSize]
        ADDVC   r0, r0, r1
        STRVC   r0, [WsPtr, #ScreenEndAddr]
        MOV     r14, #0
582
        Pull    "r0-r6"
        STRB    r14, [WsPtr, #ExternalFramestore]

        TEQ     R2, #0                  ; Shadowing or not ?
        LDR     R3, [WsPtr, #VduStatus]
        BICEQ   R3, R3, #Shadowing
        ORRNE   R3, R3, #Shadowing
        STR     R3, [WsPtr, #VduStatus]

        STRB    R2, [WsPtr, #ScreenMemoryClaimed] ; only allow ADFS to claim
                                        ; if non-shadow (simplifies things!)

        BL      ConvertBankToAddress    ; R3 := default start for this bank
        STR     R3, [WsPtr, #DriverBankAddr]
        STR     R3, [WsPtr, #DisplayBankAddr]

        LDR     R6, =ZeroPage
        ASSERT  (ZeroPage :AND: 255) = 0
        STRB    R6, [R6, #OsbyteVars + :INDEX:MemDriver]  ; indicate default
        STRB    R6, [R6, #OsbyteVars + :INDEX:MemDisplay] ; for both of these

        LDR     R6, [R13, #16+wkModeFlags]
        STR     R6, [WsPtr, #DisplayModeFlags]

; initialise any values which SwitchOutput refers to

        LDR     R4, [R13, #16+wkLineLength]
        STR     R4, [WsPtr, #DisplayLineLength]

; now set up other mode variables by calling SwitchOutput

        ADD     R3, WsPtr, #VduSaveArea+InitFlag
        MOV     R2, #0
        STR     R2, [R3]                ; indicate uninitialised
        TST     R6, #ModeFlag_Teletext
        MOVNE   R3, #0                  ; if teletext, then no save area
        MOVEQ   R3, #1                  ; else MOS's save area
        MOV     R1, #0                  ; just in case
        MOV     R0, #SpriteReason_SwitchOutputToSprite
        SWI     XOS_SpriteOp

; now create other variables from simple ones

        LDR     R3, [WsPtr, #NColour]
        STR     R3, [WsPtr, #DisplayNColour]

        ASSERT  YWindLimit = XWindLimit +4
        ASSERT  DisplayYWindLimit = DisplayXWindLimit +4
        ADD     R0, WsPtr, #XWindLimit
        LDMIA   R0, {R3, R4}
        ADD     R0, WsPtr, #DisplayXWindLimit
        STMIA   R0, {R3, R4}

        ASSERT  YEigFactor = XEigFactor +4
        ASSERT  DisplayYEigFactor = DisplayXEigFactor +4
        ADD     R0, WsPtr, #XEigFactor
        LDMIA   R0, {R3, R4}
        ADD     R0, WsPtr, #DisplayXEigFactor
        STMIA   R0, {R3, R4}

        ASSERT  Log2BPP = Log2BPC +4
        ADD     R0, WsPtr, #Log2BPC
        LDMIA   R0, {R0, R1}                    ; R0 = Log2BPC; R1 = Log2BPP
        STRB    R1, [WsPtr, #DisplayLog2BPP]
        SUB     R3, R3, R0                      ; adjust XEig for double pixels
        ADD     R3, R3, R1
        STRB    R3, [WsPtr, #PointerXEigFactor]

        LDR     R3, [R13, #16+wkModeFlags]
        STR     R3, [WsPtr, #ModeFlags]

; finished doing other variables

; tell hardware to change mode, unless already done
        LDR     r0, [r13], #16
        AND     r0, r0, #GVVetMode2_ResultMask
        CMP     r0, #GVVetMode2_Result_UnkFramestore
        ADDNE   R0, R13, #wkwordsize            ; R0 -> VIDCList3
        BLNE    HardwareModeChange

        ADD     R13, R13, #PushedInfoSize       ; junk stacked data

        ; for backward compatibility, show that video DMA is enabled in
        ; MEMC soft copy (DON'T call OS_UpdateMEMC, which would also
        ; make redundant call to HAL)
        ;
        SavePSR R2
        LDR     R0, =ZeroPage
        WritePSRc SVC_mode+I_bit+F_bit, R14
        LDR     R1, [R0, #MEMC_CR_SoftCopy]
        ORR     R1, R1, #(1 :SHL: 10)
        STR     R1, [R0, #MEMC_CR_SoftCopy]
        RestPSR R2

        BL      SetVendDefault

        LDR     R1, [WsPtr, #ScreenEndAddr]     ; need to reload cos corrupt
        LDR     R2, [WsPtr, #TotalScreenSize]
        SUB     R0, R1, R2                      ; R0 = Vstart
        BL      SetVstart
        MOV     R0, #0
        STRB    R0, [WsPtr, #PointerShapeNumber]
        STR     R0, [WsPtr, #TeletextOffset]
        STR     R0, [WsPtr, #CursorStack]       ; restore cursor on a mode

        BL      PalInit                 ; set default palette
        BL      UnblankScreen
        BL      SetMouseRectangle
        BL      FF

        MOV     R1, #Service_ModeChange
        BL      IssueModeService

        CLRV                            ; indicate no error
        Pull    PC                      ; return to caller

; *****************************************************************************
;
;       HardwareModeChange - Tell the video driver to change the mode
;
; in:   R0 = VIDC list
;
; out:  All regs preserved
;

HardwareModeChange
        Push    "R0-R4, LR"

;remember pixel rate (kHz) and border settings from VIDCList3
;
        LDR     R2, [R0, #VIDCList3_PixelRate]
        STR     R2, [WsPtr, #PixelRate]
        LDR     R2, [R0, #VIDCList3_HorizLeftBorder]
        STR     R2, [WsPtr, #BorderL]
        LDR     R2, [R0, #VIDCList3_VertiBottomBorder]
        STR     R2, [WsPtr, #BorderB]
        LDR     R2, [R0, #VIDCList3_HorizRightBorder]
        STR     R2, [WsPtr, #BorderR]
        LDR     R2, [R0, #VIDCList3_VertiTopBorder]
        STR     R2, [WsPtr, #BorderT]

;remember DPMSState (if specified) from VIDCList3
;
        MOV     R2, #0                      ; DPMSState = 0 if not specified in list
        ADD     R1, R0, #VIDCList3_ControlList
20      LDR     R3, [R1], #8                ; loop over the control parameter list
        CMP     R3, #-1
        BEQ     %FT30                       ; didn't find the DPMSState entry
        TEQ     R3, #ControlList_DPMSState
        BNE     %BT20                       ; next control parameter
        LDR     R2, [R1, #-4]               ; read DPMSState value
        AND     R2, R2, #3                  ; only bits 0,1 valid
30
        STRB    R2, [WsPtr, #ScreenBlankDPMSState]

;kernel/HAL split - call the HAL to program video controller for mode,
;
        LDR     R4, [WsPtr, #CurrentGraphicsVDriver]
        MOV     R4, R4, LSL #24
        ORR     R4, R4, #GraphicsV_SetMode
        BL      CallGraphicsV

        LDR     R4, [WsPtr, #CurrentGraphicsVDriver]
        MOV     R4, R4, LSL #24
        ORR     R4, R4, #GraphicsV_DisplayFeatures
        BL      CallGraphicsV
        STR     R0, [WsPtr, #GraphicsVFeatures] ; refresh cached features just in case something's happened to change them
        BL      UpdateFalseVsync

; claim/release memory needed for software pointer
        TST     R0, #GVDisplayFeature_HardwarePointer
        LDR     R2, [WsPtr, #SWP_Under]
        BEQ     %FT40
        TEQ     R2, #0
        MOVNE   R0, #0
        STRNE   R0, [WsPtr, #SWP_Under]
        MOVNE   R0, #ModHandReason_Free
        SWINE   XOS_Module
        B       %FT50

40
        TEQ     R2, #0
        BNE     %FT50
        ; Claim maximum amount needed
        MOV     R0, #ModHandReason_Claim
        MOV     R3, #32*32*4
        SWI     XOS_Module
        STRVC   R2, [WsPtr, #SWP_Under]
50
        ; Release mutex and reset state
        ; Note that we do this even if the pointer isn't needed, to ensure we
        ; don't get confused about its state
        MOV     R2, #0
        STR     R2, [WsPtr, #SWP_Pos]
        STRB    R2, [WsPtr, #SWP_Mutex]
        Pull    "R0-R4, PC"

        MakeErrorBlock BadMODE

        LTORG

; The following symbols, apart from being used in NColourTable,
; are also used in constructing mode selectors for mode numbers in VduModes

NColour_0       *       1
NColour_1       *       3
NColour_2       *       15
NColour_3       *       63
NColour_4       *       &FFFF
NColour_5       *       &FFFFFFFF

; *****************************************************************************
;
;       IssueModeService - Issue service (either ModeChanging or ModeChange)
;
; in:   R1 = service code
;
; out:  R1 corrupted
;

IssueModeService Entry "r2,r3"
        BL      ReadMonitorType
        LDR     r2, [WsPtr, #DisplayModeNo]
        IssueService
        EXIT

; *****************************************************************************
;
;       PushModeInfo - Push appropriate mode table and video controller params
;       onto stack, having generated it by possibly issuing service
;
; in:   R10 = mode to try for
;       R11 = mode to use if service not claimed (and R10 is mode number)
;       R10 and R11 should have bit 7 CLEAR (if mode numbers)
;
; out:  If r10 is an invalid mode selector or invalid new format sprite word then
;         V=1
;         r0 -> error
;         stack flat (no pushed info)
;       else
;         V=0
;         Stack holds a mode table (size wkwordsize) and VIDCList
;         type 3 (size VIDCList3Size) (total size PushedInfoSize)
;       endif
;       All other registers preserved
;

PushModeInfoAnyMonitor ROUT
        SUB     sp, sp, #PushedInfoSize
        Push    "r2-r4,r7-r11, lr"
        MOV     r3, #-1
        MOV     r7, #-1                 ; indicate no VIDC stuff necessary
        CMP     r10, #&100              ; is it a mode selector
        BCS     PushModeInfoCommonNoService
        BranchIfKnownMode r10, PushModeInfoCommonNoService
        B       PushModeInfoCommon

PushModeInfo ROUT
        SUB     sp, sp, #PushedInfoSize
        Push    "r2-r4,r7-r11, lr"
        MOV     r7, #0                  ; indicate VIDC stuff IS necessary
        BL      ReadMonitorType
PushModeInfoCommon
        MOV     r2, r10                 ; r2 = original mode
        BL      OfferModeExtension
        BEQ     %FT30                   ; [service claimed]

        CMP     r2, #&100               ; service not claimed - check if mode selector
        MOVCC   r10, r11                ; unrecognised mode number, so use substitute
        BCC     PushModeInfoCommonNoService

25
; service not claimed and it's a mode selector - return error "Screen mode not available"

        ADR     r0, ErrorBlock_ModeNotAvailable
26
  [ International
        BL      TranslateError
  ]
        B       %FT40

        MakeErrorBlock ModeNotAvailable

30
        TEQ     r4, #0                  ; if r4 returned zero, then a mode selector was used
                                        ; either initially or after translation from mode number
        BEQ     %FT35                   ; so no ws list
        LDR     r2, [r4, #4]!           ; else if claimed, then find ws base mode
        CMP     r2, #&100               ; if ws base mode is a mode selector, it's invalid
        ANDCC   r2, r2, #&7F            ; else knock off shadow bit
        BCC     %FT35
        MOV     r10, r11                ; invalid ws base mode, so pretend service not responded to

; and drop thru to PushModeInfoCommonNoService

PushModeInfoCommonNoService
        MOV     r2, r10                 ; else use provided mode
        MOV     r3, #0
        MOV     r4, #0
35
        ADD     r9, sp, #9*4            ; adjust for pushed registers
        CMP     r2, #&100
        BCC     %FT45
        BL      GenerateModeSelectorVars ; also copes with new sprite mode word
        BVC     %FT55

; we got an error

40
        SETV
        Pull    "r2-r4,r7-r11,lr"       ; restore registers
        ADD     sp, sp, #PushedInfoSize ; junk stack frame
        MOV     pc, lr                  ; exit VS, r0 -> error

45
        ADRL    r14, Vwstab
        LDR     r10, [r14, r2, LSL #2]
        ADD     r14, r14, r10           ; r14 -> mode table
        MOV     r10, #wkwordsize-4
50
        LDR     r2, [r14, r10]
        STR     r2, [r9, r10]
        SUBS    r10, r10, #4
        BCS     %BT50

; now change any variables specified in workspace block or mode selector overrides

55
        TEQ     r4, #0                  ; if service was claimed
        ADDNE   r4, r4, #4              ; then skip ws base mode
        BLNE    ProcessModeVarPairs


; hopefully, R7 is still set from up there to be NZ if no VIDC stuff necessary

        CMP     r7, #0
        Pull    "r2-r4,r7-r11, pc", NE  ; if no VIDC stuff required, exit (NB V=0 from CMP)

; mjs Kernel/HAL split
; pushed video controller info is now hardware independent, it is a VIDCList type 3

        TEQ     R3, #0                  ; if no module claimed service
        MOVEQ   R2, R11                 ; then use provided mode
        BEQ     %FT62
59
        ADD     R14, R9, #wkwordsize    ; R14 -> space for VIDCList3
        ASSERT  VIDCList3BaseSize = 64
        LDMIA   R3!, {R2,R4,R7-R11}     ; 28 bytes
        STMIA   R14!, {R2,R4,R7-R11}
        LDMIA   R3!, {R2,R4,R7-R11}     ; 56 bytes
        STMIA   R14!, {R2,R4,R7-R11}
        LDMIA   R3!, {R2,R4}            ; 64 bytes
        STMIA   R14!, {R2,R4}
        MOV     R10, #VIDCList3Size-(VIDCList3BaseSize+4) ; this much space left for control list items (excluding terminator)
60
        LDR     R8, [R3], #4
        SUBS    R10, R10, #8
        BLT     %FT65                   ; List too long, throw an error
        CMP     R8, #-1
        LDRNE   R9, [R3], #4
        STMNEIA R14!, {R8-R9}
        BNE     %BT60
        STR     R8, [R14]

        ; Recalculate LineLength, ScreenSize based on the contents of the VIDC
        ; list
        ADD     R9, SP, #9*4            ; adjust for pushed registers
        LDR     R10, [R9, #wkwordsize + VIDCList3_SyncPol]
        AND     R10, R10, #SyncPol_Interlace :OR: SyncPol_InterlaceFields
        TEQ     R10, #SyncPol_Interlace :OR: SyncPol_InterlaceFields
        MOVEQ   R10, #1                 ; true interlace with 2 interleaved fields
        MOVNE   R10, #0
        LDR     R2, [R9, #wkwordsize + VIDCList3_PixelDepth]
        LDR     R4, [R9, #wkwordsize + VIDCList3_HorizDisplaySize]
        MOV     R4, R4, LSL R2
        ADD     R4, R4, #7
        MOV     R4, R4, LSR #3
        ADD     R14, R9, #wkwordsize + VIDCList3_ControlList
61
        LDMIA   R14!, {R7, R8}
        CMP     R7, #ControlList_ExtraBytes
        ADDEQ   R4, R4, R8
        CMP     R7, #ControlList_Terminator
        BNE     %BT61
        STR     R4, [R9, #wkLineLength]
        LDR     R2, [R9, #wkwordsize + VIDCList3_VertiDisplaySize]
        MOV     R2, R2, LSL R10         ; Calculate height from VIDC list too (YWindLimit may have been tampered with?)
        MUL     R2, R4, R2
        LDR     R4, [R9, #wkModeFlags]
        TST     R4, #ModeFlag_Teletext
        MOVNE   R2, R2, LSL #1          ; teletext ScreenSize is actually enough for two screens
        STR     R2, [R9, #wkScreenSize]

        CMP     R10, #0
        ORRNE   R4, R4, #ModeFlag_InterlacedMode
        STRNE   R4, [R9, #wkModeFlags]  ; Make sure interlace is flagged as such

        LDR     R2, [WsPtr, #GraphicsVFeatures]
        TST     R2, #GVDisplayFeature_HardwareScroll
        ORREQ   R4, R4, #ModeFlag_HardScrollDisabled
        STREQ   R4, [R9, #wkModeFlags]

        CLRV
        Pull    "R2-R4,R7-R11, PC"      ; done

62                                      ; arrive here if service not claimed, R2 = provided mode number
        ; XXX possible to exit earlier? (right after OfferModeExtension?)
        B       %BT25

65
        ADRL    r0, ErrorBlock_BuffOverflow
        B       %BT26

; *****************************************************************************
;
;       GenerateModeSelectorVars - Work out mode variables from mode selector
;                                    or new format sprite mode word
;
; Note: the algorithms used to generate these variables are duplicated in
; s.vdu.vduswis in the OS_ReadModeVariable code.

; in:   r2 = new format sprite word or pointer to mode selector
;       r9 -> stack frame to store vars in
;
; out:  If valid then
;           V=0
;           r0 preserved
;       else
;           V=1
;           r0 -> error
;       endif
;       All other registers preserved
;

GenerateModeSelectorVars Entry "r0,r1,r3-r8,r10-r12"
        ASSERT  ModeSelector_Flags = 0
        ASSERT  ModeSelector_XRes = 4
        ASSERT  ModeSelector_YRes = 8
        ASSERT  ModeSelector_PixelDepth = 12
        TST     r2, #1                          ; is it a new format sprite mode word?
        BNE     %FT10                           ; [yes, so skip]
        MOV     r0, r2
        BL      ValidateModeSelector
        BVS     %FT95                           ; invalid - return error
        LDMIB   r2, {r4-r6}                     ; r4 = xres; r5 = yres; r6 = pixdepth

        STR     r6, [r9, #wkLog2BPC]            ; log2bpc = log2bpp = pixdepth
        STR     r6, [r9, #wkLog2BPP]
        ADR     lr, NColourTable
        LDR     lr, [lr, r6, LSL #2]            ; load NColour value
        STR     lr, [r9, #wkNColour]

        MOV     lr, #0
        STR     lr, [r9, #wkYShftFactor]        ; yshftfactor = 0 (obsolete)
        STR     lr, [r9, #wkModeFlags]          ; modeflags = 0

        MOV     lr, #1
        STR     lr, [r9, #wkXEigFactor]         ; xeig = 1
        CMP     r5, r4, LSR #1                  ; if yres < xres/2
        MOVCC   r7, #2                          ; then yeig = 2
        MOVCS   r7, #1                          ; else yeig = 1
        STR     r7, [r9, #wkYEigFactor]

        RSB     r7, lr, r4, LSR #3              ; scrrcol = (xres >> 3) -1
        STR     r7, [r9, #wkScrRCol]
        RSB     r7, lr, r5, LSR #3              ; scrbrow = (yres >> 3) -1
        STR     r7, [r9, #wkScrBRow]

        SUB     r7, r4, #1
        STR     r7, [r9, #wkXWindLimit]         ; xwindlimit = xres-1
        SUB     r7, r5, #1
        STR     r7, [r9, #wkYWindLimit]         ; ywindlimit = yres-1

        MOV     r7, r4, LSL r6                  ; r7 = xres << pixdepth
        MOV     lr, r7, LSR #3
        STR     lr, [r9, #wkLineLength]         ; linelen = (xres << pixdepth) >> 3

        MUL     r7, r5, r7                      ; r7 = (xres * yres) << pixdepth
        MOV     lr, r7, LSR #3
        STR     lr, [r9, #wkScreenSize]         ; screensize = ((xres * yres) << pixdepth) >> 3

        ADD     r4, r2, #ModeSelector_ModeVars  ; now do pairs of mode variables
        BL      ProcessModeVarPairs

        ; Fixup teletext modes
        LDR     r0, [r9, #wkModeFlags]
        TST     r0, #ModeFlag_Teletext
        BEQ     %FT09
        ; Force the mode flags to be correct for our implementation
      [ HiResTTX
        ORR     r0, r0, #ModeFlag_NonGraphic+ModeFlag_GapMode+ModeFlag_DoubleVertical
      |
        BIC     r0, r0, #ModeFlag_DoubleVertical
        ORR     r0, r0, #ModeFlag_NonGraphic+ModeFlag_GapMode
      ]
        STR     r0, [r9, #wkModeFlags]
        ; Ensure colour depth is acceptable
        LDR     r0, [r9, #wkNColour]
        CMP     r0, #15
      [ HiResTTX
        ADRLOL  r0, ErrorBlock_BadPixelDepth
        BLO     %FT90
        CMP     r0, #63
        ADREQL  r0, ErrorBlock_BadPixelDepth
        BEQ     %FT90
      |
        ADRNEL  r0, ErrorBlock_BadPixelDepth
        BNE     %FT90
      ]
        ; Massage ScreenSize, needs to be large enough for two screen banks
        LDR     r0, [r9, #wkScreenSize]
        MOV     r0, r0, LSL #1
        STR     r0, [r9, #wkScreenSize]
        ; Clamp ScrRCol, ScrBRow
        LDR     r0, [r9, #wkScrRCol]
        CMP     r0, #254 ; TTXDoubleCounts is a byte array
        MOVHI   r0, #254
        LDR     r7, [r2, #ModeSelector_XRes]
        MOV     lr, #1
      [ HiResTTX
        RSB     r7, lr, r7, LSR #4
      |
        RSB     r7, lr, r7, LSR #3
      ]
        CMP     r0, r7
        MOVHI   r0, r7
        STR     r0, [r9, #wkScrRCol]
        LDR     r0, [r9, #wkScrBRow]
      [ HiResTTX
        DivRem  r7, r5, #20, lr, norem
      |
        DivRem  r7, r5, #10, lr, norem
      ]
        CMP     r0, r7
        SUBHS   r0, r7, #1
        STR     r0, [r9, #wkScrBRow]

        CLRV
        EXIT

09
        ; We only have double-vertical char plotting routines for 1bpp
        LDR     r0, [r9, #wkModeFlags]
        LDR     r1, [r9, #wkLog2BPP]
        LDR     r3, [r9, #wkLog2BPC]
        TST     r0, #ModeFlag_DoubleVertical
        BEQ     %FT091
        CMP     r1, #0
        ; Also can't mix double-vertical with double-pixel, since that would also require another char plotting routine
        CMPEQ   r3, #0
        ADRNEL  r0, ErrorBlock_BadPixelDepth
        BNE     %FT90
091
        ; We only support 2bpp BBC gap modes
        TST     r0, #ModeFlag_BBCGapMode
        CMPNE   r1, #1
        ADRNEL  r0, ErrorBlock_BadPixelDepth
        BNE     %FT90
        ; We don't support double-pixel modes higher than 16bpp (or regular modes higher than 32bpp)
        CMP     r3, #5
        ADRHIL  r0, ErrorBlock_BadPixelDepth
        BHI     %FT90

        CLRV
        EXIT


; store info for new format sprite word in stack frame

10
        MOV     r0, #0
        STR     r0, [r9, #wkYShftFactor]        ; yshftfactor = 0
        STR     r0, [r9, #wkModeFlags]          ; modeflags = 0

        AND     r0, r2, #15<<27                 ; get type
        CMP     r0, #SpriteType_RISCOS5<<27     ; RISC OS 5 type?
        BEQ     %FT50

        CMP     r0, #SpriteType_New64K<<27      ; 64K colour sprite?
        MOVEQ   r0, #ModeFlag_64k
        STREQ   r0, [r9, #wkModeFlags]          ; Make a note of it
        MOVEQ   r0, #SpriteType_New16bpp        ; ... and treat as regular 16bpp
        MOVNE   r0, r0, LSR #27

15
      [ NoARMT2
        MOV     r1, r2, LSL #(31-13)
        MOV     r1, r1, LSR #(31-13)+1          ; extract xdpi (bits 1..13)
      |
        UBFX    r1, r2, #1, #13                 ; extract xdpi (bits 1..13)
      ]

        TEQ     r1, #180                        ; 180 => xeig=0
        MOVEQ   r1, #0
        BEQ     %FT20

        TEQ     r1, #22                         ; 22/23 => xeig=3
        TEQNE   r1, #23
        MOVEQ   r1, #3
        BEQ     %FT20

        TEQ     r1, #(45 :SHL: 2), 2            ; check if 45   (EQ,CC if so)
        CMPNE   r1, #90                         ; or 90         (EQ,CS if so)
        BNE     %FT85
        MOVCC   r1, #2                          ; 45 => xeig=2
        MOVCS   r1, #1                          ; 90 => xeig=1
20
        STR     r1, [r9, #wkXEigFactor]

      [ NoARMT2
        MOV     r1, r2, LSL #(31-26)
        MOV     r1, r1, LSR #(31-26)+14         ; extract ydpi (bits 14..26)
      |
        UBFX    r1, r2, #14, #13                ; extract ydpi (bits 14..26)
      ]

        TEQ     r1, #180                        ; 180 => yeig=0
        MOVEQ   r1, #0
        BEQ     %FT21

        TEQ     r1, #22                         ; 22/23 => yeig=3
        TEQNE   r1, #23
        MOVEQ   r1, #3
        BEQ     %FT21

        TEQ     r1, #(45 :SHL: 2), 2            ; check if 45   (EQ,CC if so)
        CMPNE   r1, #90                         ; or 90         (EQ,CS if so)
        BNE     %FT85
        MOVCC   r1, #2                          ; 45 => yeig=2
        MOVCS   r1, #1                          ; 90 => yeig=1
21
        STR     r1, [r9, #wkYEigFactor]


25
        CMP     r0, #SpriteType_MAX             ; check for legality - NB type 0 is impossible here because r2>=&100
        MOVCS   r0, #SpriteType_Substitute      ; substitute if unknown
        ADRL    lr, NSM_bpptable-4
        LDR     r0, [lr, r0, LSL #2]            ; get the bpp from table

        ADR     r1, NColourTable
        LDR     r1, [r1, r0, LSL #2]
        STR     r1, [r9, #wkNColour]

30
        STR     r0, [r9, #wkLog2BPC]
        STR     r0, [r9, #wkLog2BPP]

        CLRV
        EXIT

50
        TST     r2, #&F0000                     ; validate RO 5 sprite mode word
        TSTEQ   r2, #&0000E
        MOVNE   r0, #SpriteType_Substitute      ; and try substitute if bad
        BNE     %BT15

      [ NoARMT2
        MOV     r1, r2, LSR #4
        AND     r1, r1, #3                      ; extract XEigFactor (bits 4..5)
      |
        UBFX    r1, r2, #4, #2                  ; extract XEigFactor (bits 4..5)
      ]
        STR     r1, [r9, #wkXEigFactor]

      [ NoARMT2
        MOV     r1, r2, LSR #6
        AND     r1, r1, #3                      ; extract YEigFactor (bits 6..7)
      |
        UBFX    r1, r2, #6, #2                  ; extract YEigFactor (bits 6..7)
      ]
        STR     r1, [r9, #wkYEigFactor]

        AND     r1, r2, #&FF00                  ; extract ModeFlags
        ; Validate ModeFlags. We only support RGB colourspace, so the only valid
        ; bits are the RGB and alpha flags
        TST     r1, #&FF00-(ModeFlag_DataFormatSub_RGB+ModeFlag_DataFormatSub_Alpha)
        MOVNE   r0, #SpriteType_Substitute      ; try the substitute?
        BNE     %BT15

        MOV     r0, r2, LSR #20
        ANDS    r0, r0, #127                    ; extract type
        MOVEQ   r0, #SpriteType_Substitute      ; type 0 isn't valid!

        CMP     r0, #SpriteType_New64K
        ORREQ   r1, r1, #ModeFlag_64k
        MOVEQ   r0, #SpriteType_New16bpp

        CMP     r0, #SpriteType_New16bpp        ; for palettised modes
        CMPLT   r0, r1                          ; flags must be zero
        MOVLT   r0, #SpriteType_Substitute
        BLT     %BT15

        STR     r1, [r9, #wkModeFlags]

        CMP     r0, #SpriteType_New4K
        BNE     %BT25

        LDR     r0, =4095
        STR     r0, [r9, #wkNColour]
        MOV     r0, #4
        B       %BT30

85
        ADR     r0, ErrorBlock_Sprite_BadDPI
90
 [ International
        BL      TranslateError
 ]
95
        STR     r0, [sp]                        ; update saved r0
        SETV                                    ; indicate error
        EXIT

NColourTable    &       NColour_0, NColour_1, NColour_2
                &       NColour_3, NColour_4, NColour_5
                &       NColour_5 ; CMYK sprite (not supported)
                &       NColour_5 ; 24bpp sprite (not supported)
                &       NColour_5 ; JPEG sprite (not supported)
                &       NColour_4 ; 64K sprite
                &       NColour_5, NColour_5, NColour_5, NColour_5, NColour_5 ; sprite types 11-15
                &       4095 ; 4K sprite
                &       NColour_5, NColour_5 ; YCbCr 422 & 420 (not supported)
                ASSERT  . - NColourTable = (SpriteType_RO5MAX-1)*4

        MakeErrorBlock  BadPixelDepth
        MakeErrorBlock  Sprite_BadDPI

; *****************************************************************************
;
;       ValidateModeSelector - Check a mode selector is valid
;
; in:   r0 -> mode selector
;
; out:  If OK, then
;         V=0
;         All registers preserved
;       else
;         V=1
;         r0 -> error
;         All other registers preserved
;       endif
;

ValidateModeSelector Entry
        LDR     lr, [r0, #ModeSelector_Flags]
        AND     lr, lr, #ModeSelectorFlags_FormatMask
        TEQ     lr, #ModeSelectorFlags_ValidFormat
        ADRNE   r0, ErrorBlock_BadMSFlags
        BNE     %FT90
        LDR     lr, [r0, #ModeSelector_PixelDepth]
        CMP     lr, #6
        ADRCS   r0, ErrorBlock_BadPixelDepth
        BCS     %FT90
        CLRV
        EXIT

90
  [ International
        BL      TranslateError
  |
        SETV
  ]
        EXIT

        MakeErrorBlock BadMSFlags


; *****************************************************************************
;
;       ProcessModeVarPairs - Modify stacked variable info from
;                              mode variable (index, value) pairs
;
;       Internal routine used to do Service_ModeExtension workspace lists and
;        mode selectors
;
; in:   r4 -> first pair (may be none)
;       r9 -> stack frame
;
; out:  All registers preserved

ProcessModeVarPairs Entry "r4, r8, r10"
        ADRL    r14, RMVTab
10
        LDR     r10, [r4], #4           ; get next entry in ws table
        CMP     r10, #-1
        EXIT    EQ                      ; no more to do
        CMP     r10, #(SWIRVVTabModeEnd-SWIRVVTab) ; is it a mode variable ?
        BCS     %BT10                   ; no, then ignore
        LDRB    r10, [r14, r10]         ; load index out of RMVTab
        MOVS    r10, r10, LSR #1        ; shift byte/word flag into carry
        LDR     r8, [r4], #4            ; load value
        STRCCB  r8, [r9, r10]           ; either store byte
        STRCS   r8, [r9, r10]           ; or store word
        B       %BT10

; *****************************************************************************
;
;       OfferModeExtension - Issue mode extension service
;
; in:   R2 = mode specifier
;       R3 = monitor type
;
; out:  EQ => service claimed, R3 -> VIDC list, R4 -> workspace list
;       NE => service not claimed, R3,R4 preserved
;       All other registers preserved
;

OfferModeExtensionAnyMonitor ROUT
        MOV     r3, #-1
OfferModeExtension ROUT
        Push    "r1,r2,r4,r5,r14"

; TMD 10-Jan-94 - added code here to check for erroneous passing in of a sprite mode word.
; This prevents data aborts when modules try to index off a bad address.
;
; We could have done OS_ValidateAddress, but that would be rather slow, and mode selectors
; are of indeterminate length.
;
; If we detect one of these, we pretend the service wasn't claimed. Hopefully this should
; ensure that the mode change returns an error.

; Fixes bug MED-00483.

        BICS    r14, r2, #&FF                   ; NE if not a mode number
        TSTNE   r2, #3                          ; NE if not a mode number, but invalid mode selector
        Pull    "r1,r2,r4,r5,pc", NE            ; so exit NE, pretending that service not claimed

        GetBandwidthAndSize     r4, r5
        MOV     r1, #Service_ModeExtension
        IssueService
        TEQ     r1, #0                          ; if service claimed
        CMPNE   r3, #-1                         ; or if "don't care" monitortype
        BEQ     %FT90                           ; then we can't do any more

        CMP     r2, #&100                       ; if it's a mode selector
        BCS     %FT90                           ; then we can't help them either
        BranchIfNotKnownMode r2, %FA90          ; if we don't recognise screen mode number we can't either

; it is a known numbered mode, so create a mode selector on the stack that we can pass to service

        Push    "r6,r7"
        SUB     sp, sp, #ModeSelector_ModeVars+4        ; make room for block including terminator
        ADRL    r6, Vwstab
        LDR     r14, [r6, r2, LSL #2]
        ADD     r6, r6, r14
        BL      ModeNumberToModeSelector                ; R1, R7 corrupt
        MOV     r1, #Service_ModeExtension

        MOV     r2, sp
        IssueService
        TEQ     r1, #0
        BEQ     %FT10                                   ; service was claimed

; not claimed, so try again with -1 as frame rate

        MOV     r7, #-1
        STR     r7, [sp, #ModeSelector_FrameRate]
        IssueService
        TEQ     r1, #0
10
        ADD     sp, sp, #ModeSelector_ModeVars+4        ; junk mode selector
        Pull    "r6, r7"
90
        CMP     r2, #&100                               ; if we started or ended up with a mode selector
        MOVCS   r4, #0                                  ; then return r4 = 0 (if claimed)

        TEQ     r1, #0
        STREQ   r4, [sp, #2*4] ; if service claimed, then return r4 from service, else preserve it
        Pull    "r1,r2,r4,r5,pc"


; *****************************************************************************
;
; ETB - Redefine character
; ===   & other stuff
;
;       VDU 23,0,r,v,0|         Talk to 6845 !
;       VDU 23,1,n,m,r,g,b|     Program cursor
;       VDU 23,2,n1..n8         Ecf pattern 1
;       VDU 23,3,n1..n8         Ecf pattern 2
;       VDU 23,4,n1..n8         Ecf pattern 3
;       VDU 23,5,n1..n8         Ecf pattern 4
;       VDU 23,6,n1..n8         Dot dash line style
;       VDU 23,7,m,d,z|         Scroll window directly
;       VDU 23,8,t1,t2,x1,y1,x2,y2|     Clear block
;       VDU 23,9,n|             Set 1st flash time
;       VDU 23,10,n|            Set 2nd flash time
;       VDU 23,11|              Default Ecf patterns
;       VDU 23,12,n1..n8        Ecf pattern 1 (simple setting)
;       VDU 23,13,n1..n8        Ecf pattern 2 (simple setting)
;       VDU 23,14,n1..n8        Ecf pattern 3 (simple setting)
;       VDU 23,15,n1..n8        Ecf pattern 4 (simple setting)
;       VDU 23,16,x,y|          Cursor movement control
;       VDU 23,17,c,t|          Set colour tints, ECF info, char sizes
;
;
ETB
        LDRB    R0, [WsPtr, #QQ+0]
        CMP     R0, #32         ; defining a normal character ?
        BCS     DefineChar
        LDR     R2, [PC, R0, LSL #2]
        ADD     PC, PC, R2      ; enters routine with R0 => byte after 23


ETBtab
        &       Vdu23_0-ETBtab-4
        &       Vdu23_1-ETBtab-4
        &       ComplexEcfPattern-ETBtab-4
        &       ComplexEcfPattern-ETBtab-4
        &       ComplexEcfPattern-ETBtab-4
        &       ComplexEcfPattern-ETBtab-4
        &       LineStyle-ETBtab-4
        &       Vdu23_7-ETBtab-4
        &       Vdu23_8-ETBtab-4
        &       Vdu23_9-ETBtab-4
        &       Vdu23_10-ETBtab-4
        &       DefaultEcfPattern-ETBtab-4
        &       SimpleEcfPattern-ETBtab-4
        &       SimpleEcfPattern-ETBtab-4
        &       SimpleEcfPattern-ETBtab-4
        &       SimpleEcfPattern-ETBtab-4
        &       Vdu23_16-ETBtab-4
        &       Vdu23_17-ETBtab-4
        &       Vdu23_18-ETBtab-4
        &       Vdu23_19-ETBtab-4
        &       Vdu23_20-ETBtab-4
        &       Vdu23_21-ETBtab-4
        &       Vdu23_22-ETBtab-4
        &       Vdu23_23-ETBtab-4
        &       Vdu23_24-ETBtab-4
        &       Vdu23_25-ETBtab-4
        &       Vdu23_26-ETBtab-4
        &       Vdu23_27-ETBtab-4
        &       Vdu23_28-ETBtab-4
        &       Vdu23_29-ETBtab-4
        &       Vdu23_30-ETBtab-4
        &       Vdu23_31-ETBtab-4

; NB All other labels for Vdu23 are in TMD files so I don't have to pester RCM

;Vdu23_18       ; Assigned to Teletext operations
Vdu23_19
Vdu23_20
Vdu23_21
Vdu23_22
Vdu23_23
Vdu23_24
Vdu23_25
Vdu23_26
; Vdu23_27      ; Assigned to Richard (well some of it, anyway)
Vdu23_28
Vdu23_29
Vdu23_30
Vdu23_31
UnknownVdu23

; R0 already contains first parameter to VDU23

        MOV     R10, #UKVDU23V
        Push    "WsPtr, R14"            ; calling a vector corrupts R12
        BL      VduQQVec                ; so we have to preserve it
        Pull    "WsPtr, PC", VC         ; before we return to PostWrchCursor

; error in UKVDU23 vector, so go to vdu error exit

        Pull    "WsPtr, R14"
        B       VduBadExit

; *****************************************************************************
;
; DLE
; CLG - Clear graphics window
; ===
;
; On exit, R0..R11 corrupt
;
DLE
CLG     ROUT
        GraphicsMode R0
        MOVNE   PC,LR                   ; check for graphics mode (changed by DDV 15/9/92)

        ADD     R0, WsPtr, #BgEcfOraEor ; point at background colour
        STR     R0, [WsPtr, #GColAdr]
        ADD     R11, WsPtr, #GWLCol     ; load window coordinates into
        LDMIA   R11, {R0-R3}            ; RectFill's parameter space

        LDR     R6, [WsPtr, #CursorFlags] ; if clip box is not enabled
        TST     R6, #ClipBoxEnableBit   ; then goto code directly
        BEQ     RectFillA

        Push    R14                     ; else merge graphics window (in R0-R3)
        BL      MergeClipBox            ; with clip box
        Pull    R14
        B       RectFillA

; *****************************************************************************
;
; DC2
; GCol - Set Graphics action and colour
; ====
;
; On entry, R0 holds GCol action
;           R1 holds GCol colour, 0..127 means program fg
;                                 128..255 means program bg
;
; In 256-colour modes, the extra colours are accessed via TINT
;
DC2
GCol    ROUT
        LDRB    R0, [WsPtr, #QQ+0]      ; GCol action, eg store, eor etc.
        LDRB    R1, [WsPtr, #QQ+1]      ; GCol colour
        LDR     R2, [WsPtr, #NColour]   ; number of colours-1
        TST     R1, #&80
        AND     R1, R1, R2              ; limit colour to range available

        STREQ   R0, [WsPtr, #GPLFMD]    ; GCOL(a,0..127) is foreground
        STREQ   R1, [WsPtr, #GFCOL]

        STRNE   R0, [WsPtr, #GPLBMD]    ; GCOL(a,128..255) is background
        STRNE   R1, [WsPtr, #GBCOL]
                                        ; drop into SetColour to....

; SetColour - Setup FgEcf & BgEcf, used after GCOL or setting of Ecfs
; =========   or default palette (does a gcol).

SetColour
        Push    R14
        LDR     R1, [WsPtr, #GPLFMD]    ; setup FgEcf, maybe solid or Ecf
        LDR     R0, [WsPtr, #GFCOL]
        ADD     R2, WsPtr, #FgEcf
        LDR     R3, [WsPtr, #GFTint]    ; tint only used in 256 colour modes
        ADD     R4, WsPtr, #FgPattern   ; used if OS_SetColour call
        BL      SetCol10

        LDR     R1, [WsPtr, #GPLBMD]    ; and BgEcf
        LDR     R0, [WsPtr, #GBCOL]
        ADD     R2, WsPtr, #BgEcf
        LDR     R3, [WsPtr, #GBTint]
        ADD     R4, WsPtr, #BgPattern   ; used if OS_SetColour call
        BL      SetCol10

        ADD     R0, WsPtr, #FgEcf       ; setup FgEcfOraEor
        ADD     R1, WsPtr, #BgEcf
        ADD     R2, WsPtr, #FgEcfOraEor
        LDR     R3, [WsPtr, #GPLFMD]
        BL      SetCol60

        ADD     R0, WsPtr, #BgEcf       ; and BgEcfOraEor
        ADD     R1, WsPtr, #FgEcf
        ADD     R2, WsPtr, #BgEcfOraEor
        LDR     R3, [WsPtr, #GPLBMD]
        BL      SetCol60

        ADD     R0, WsPtr, #BgEcf       ; and BgEcfStore
        ADD     R1, WsPtr, #FgEcf
        ADD     R2, WsPtr, #BgEcfStore
        MOV     R3, #0
        BL      SetCol60

        Pull    PC

; SetCol10 - Internal to SetColour
;          Build up an Ecf, given action and colour numbers
;
; On entry, R0 holds colour (0..255, where 127..255 means 0..127)
;           R1 holds action (may indicate ecf)
;           R2 holds address to write data (FgEcf or BgEcf)
;           R3 holds tint information (only used in 256 colour modes)
;           R4 holds pointer to suitable pattern table for OS_SetColour call

SetCol10 ROUT
        ANDS    R1, R1, #&F0            ; actions >=16 mean Ecf
        BNE     SetCol30

        Push    R14
        LDR     R4, [WsPtr, #NColour]   ; else use given colour number
        AND     R0, R0, R4
        AND     R0, R0, #63             ; another bodge in the house of bodges
        TST     R4, #&F0                ; if 256 colour (ie 8bpp)
        BLNE    AddTintToColour         ; then combine tint with colour

; R0 contains colour number for current mode

        LDR     LR, [WsPtr, #BitsPerPix]
10
        TEQ     LR, #32
        ORRNE   R0, R0, R0, LSL LR      ; replicate again
        MOVNE   LR, LR, LSL #1          ; doubling the shift for each pass
        BNE     %BT10

        STR     R0, [R2]
        STR     R0, [R2, #4]
        STR     R0, [R2, #8]
        STR     R0, [R2, #12]
        STR     R0, [R2, #16]
        STR     R0, [R2, #20]
        STR     R0, [R2, #24]
        STR     R0, [R2, #28]

        Pull    "PC"

; R1 = ecf number as 16,32,48,64,80,96
; R2 -> destination
; R4 -> pattern block (if R1 =96!)

SetCol30
        CMP     R1, #96                 ; special internal plot?
        MOVCS   R0, R4                  ; yes, so point at pattern to be copied
        MOVCS   R3, #1                  ; col step =1
        MOVCS   R4, #0                  ; row step =0 (already there!)
        BCS     SetCol35

        CMP     R1, #80                 ; 80 => giant ecf (>80 does same)
        ADDCS   R0, WsPtr, #Ecf1        ; then point at ecf0
        MOVCS   R3, #8                  ; col step=8
        MOVCS   R4, #(1-8*4)            ; row step, back to 1st ecf on 1 byte
        BCS     SetCol35

        ADD     R0, WsPtr, #(Ecf1-8)
        ADD     R0, R0, R1, LSR #1      ; else point R0 at Ecf1,2,3 or 4

        LDR     R4, [WsPtr, #BitsPerPix]
        CMP     R4, #16
        BHI     SetCol45
        BEQ     SetCol40
        LDR     R5, [WsPtr, #BytesPerChar]
        TEQ     R4, R5                  ; if BitsPerPix <> 'fudged' BitsPerPix
        BNE     SetCol52                ; then double up the pixels
                                        ; else its a normal Ecf
        MOV     R3, #0                  ; col step=0, same byte each coloum
        MOV     R4, #1                  ; row step=1
SetCol35
        MOV     R5, #8                  ; do 8 rows
SetCol36
        MOV     R6, #4                  ; of 4 columns
SetCol37
        LDRB    R7, [R0], R3            ; read from source & move by col step
        STRB    R7, [R2], #1            ; write to dest, update dest pointer
        SUBS    R6, R6, #1
        BNE     SetCol37
        ADD     R0, R0, R4              ; step source pointer to next row
        SUBS    R5, R5, #1
        BNE     SetCol36
        MOV     PC, R14

; Generate 1x4 pattern for 16bpp
;
; R0 points to Ecf(n)
; R2 points to destination
;
; Uses R0,R2,R5,R6,R7
;
SetCol40
        LDMIA   R0, {R0,R6}             ; Grab full pattern block
        EOR     R5, R0, R0, ROR #16     ; &22221111 ^ &11112222
        EOR     R7, R6, R6, ROR #16
        EOR     R0, R0, R5, LSL #16     ; = &11111111
        EOR     R6, R6, R7, LSL #16
        EOR     R5, R5, R0              ; = &22222222
        EOR     R7, R7, R6
        STMIA   R2!,{R0,R5,R6,R7}       ; Store 4 rows
        STMIA   R2!,{R0,R5,R6,R7}       ; 8 rows
        MOV     PC, R14

; Generate 1x2 pattern for 32bpp
;
; R0 points to Ecf(n)
; R2 points to destination
;
; Uses R0,R2,R5,R6,R7
;
SetCol45
        LDMIA   R0, {R0,R5}
        MOV     R6,R0
        MOV     R7,R5
        STMIA   R2!,{R0,R5,R6,R7}
        STMIA   R2!,{R0,R5,R6,R7}
        MOV     PC, R14

; Double up the pixels for Mode2 etc
;
; R0 points to Ecf(n)
; R2 points to destination
;
; Uses
;
;     R3  - NColour used as PixMsk
;     R4  - BitsPerPix
;     R5  - BytesPerChar (unused)
;     R6  - byte cntr 7..0
;     R7  - source byte
;     R8  - ExtrtShftFact
;     R9  - InsrtShftFact
;     R10 - result word
;     R11 - temp

SetCol52
        LDR     R3, [WsPtr, #NColour]   ; mask for extracting pixels from ecf
        TST     R3, #&F0                ; ** if 256 colour mode
        MOVNE   R3, #&FF                ; ** then use &FF (TMD 25-Mar-87)
        LDR     R4, [WsPtr, #BitsPerPix]
        MOV     R6, #7                  ; 8 bytes/rows to do
SetCol54
        LDRB    R7, [R0, R6]            ; get byte of Ecf(n)
        RSB     R8, R4, #8
        RSB     R9, R4, #32
        MOV     R10, #0                 ; clear result word
SetCol57
        AND     R11, R3, R7, ROR R8     ; extract 1 pixel from Ecf
        ORR     R10, R10, R11, LSL R9   ; double it into result word
        SUB     R9, R9, R4
        ORR     R10, R10, R11, LSL R9

        SUB     R8, R8, R4
        AND     R8, R8, #7

        SUBS    R9, R9, R4
        BGE     SetCol57                ; process next pixel in result word

        STR     R10, [R2, R6, LSL #2]   ; write expanded word to (Fg/Bg)Ecf

        SUBS    R6, R6, #1
        BGE     SetCol54                ; process next row/byte
        MOV     PC, R14

; Tables of full colours for 2,4 & 16 colour modes (256 colour modes
; use colour number directly).
;
; N.B. these are tables of bytes

TBFullCol
        =       &FF                     ; not used - remove sometime
                                        ; (DJS comment: don't bother!)
        =       &00, &FF                ; 2 colour mode
        =       &00, &55, &AA, &FF      ; 4 colour mode

        =       &FF, &FF, &FF, &FF      ; not used but cannot be removed
        =       &FF, &FF, &FF, &FF      ; (8 colour mode!)

        =       &00, &11, &22, &33      ; 16 colour mode
        =       &44, &55, &66, &77
        =       &88, &99, &AA, &BB
        =       &CC, &DD, &EE, &FF

        ALIGN

; *****************************************************************************
;
;       SetCol60 - Build up an ecf, ORed and EORed appropriate to GCOL action
;
;       Internal to SetColour
;
; in:   R0 -> ecf colour (FgEcf/BgEcf)
;       R1 -> transparent colour (BgEcf/FgEcf)
;       R2 -> destination FgEcfOraEor/BgEcfOraEor/BgEcfStore
;       R3 = gcol action number (may indicate ecf)
;
; uses: R4 = index into ecf (7..0)
;       R5 = ecf colour word
;       R6 = transparency mask, set to &FFFFFFFF for NO transparency
;       R7 -> zgoo..zgee for gcol action
;       R8 = mask for pixel under examination
;       R9 = shift factor to move mask to next pixel (BytesPerChar)
;       R10, R11 temporary
;

SetCol60 ROUT
        MOV     R4, #7                          ; 7..0 words to process
        AND     R3, R3, #&F                     ; extract action bits
        AND     R11, R3, #7                     ; 0-7 Store etc
                                                ; 8-15 ditto with transparency
        MOV     R11, R11, LSL #2                ; 4 bits for each
        LDR     R7, =TBscrmasks
        MOV     R7, R7, ROR R11                 ; relevant bits are in top 4
        AND     R7, R7, #&F0000000              ; isolate these bits (N,Z,C,V)
SetCol70
        LDR     R5, [R0, R4, LSL #2]            ; get ecf word
        TST     R3, #8                          ; if action < 8
        MOVEQ   R6, #&FFFFFFFF
        BEQ     SetCol90                        ; then not transparent
                                                ; else build transparency mask
        LDR     R8, [WsPtr, #RAMMaskTb]         ; fetch mask for leftmost pixel
        LDR     R9, [WsPtr, #BytesPerChar]      ; shift factor for next pixel
        LDR     R6, [R1, R4, LSL #2]            ; get 'transparent' colour
        EOR     R6, R6, R5
SetCol80
        TST     R6, R8                          ; if pixels the same,
                                                ; then it's transparent
        ORRNE   R6, R6, R8                      ; else set mask to plot it
        MOVS    R8, R8, LSL R9
        BNE     SetCol80

SetCol90
        MSR     CPSR_f, R7                      ; put bits into N, Z, C, V
                                                ;              OO,EO,OE,EE

        MOVCC   R10, R5                         ; if ORing with &00000000
        MOVCS   R10, #&FFFFFFFF                 ; if ORing with &FFFFFFFF
        MVNVS   R10, R10                        ; if EORing with &FFFFFFFF

;       MOVPL   R5, R5                          ; if ORing with &00000000
        MOVMI   R5, #&FFFFFFFF                  ; if ORing with &FFFFFFFF
        MVNEQ   R5, R5                          ; if EORing with &FFFFFFFF

; now R5 = OR mask, R10 = EOR mask

        AND     R5, R5, R6                      ; then clear 'transparent'
        AND     R10, R10, R6                    ; pixels

        LDR     R11, [WsPtr, #ECFShift]
        MOV     R5, R5, ROR R11                 ; rotate OR and EOR masks
        MOV     R10, R10, ROR R11               ; to correct for ECF X origin

        LDR     R11, [WsPtr, #ECFYOffset]
        ADD     R11, R11, R4                    ; add on ECF Y offset
        AND     R11, R11, #7                    ; and wrap

        ADD     R11, R2, R11, LSL #3
        STMIA   R11, {R5, R10}                  ; write to (Fg/Bg)EcfOraEor

        SUBS    R4, R4, #1
        BCS     SetCol70

        MOV     PC, R14

; *****************************************************************************
;
;       AddTintToColour - in 256 colour modes
;
;       Internal to SetColour (derived from TMD's FudgeColour)
;
; in:   R0 = colour (0..255), where 6 LSBits are used
;       R3 = tint
;
; out:  R0 holds colour byte with tint added
;       R1-R3 preserved
;       R4 undefined
;       PSR preserved
;

        !       0,"WARNING: AddTintToColour returns > 8 bit values now, check ECF handling!"

AddTintToColour
        Push    "R3,LR"
        AND     R0, R0, #63             ; extract suitable set of bits
        AND     R3, R3, #192            ; and another set
        ORR     R0, R0, R3
        BL      ConvertGCOLToColourNumber
        Pull    "R3,PC"

; *****************************************************************************
;
;       CAN - Define graphics window
;
;       External routine
;
; in:   The window is given by bytes in the vdu queue, as follows :-
;         QQ+0 = leftLo
;         QQ+1 = leftHi
;         QQ+2 = bottomLo
;         QQ+3 = bottomHi
;         QQ+4 = rightLo
;         QQ+5 = rightHi
;         QQ+6 = topLo
;         QQ+7 = topHi
;
;       These are relative to the current graphics origin.
;       The resultant window must obey the following rules :-
;         RCol >= LCol
;         TRow >= BRow
;         LCol >= 0
;         BRow >= 0
;         YWindLimit >= TRow
;         XWindLimit >= RCol
;

CAN     ROUT
        Push    R14
        ADD     R8, WsPtr, #GCsX        ; save ECursor away, cos EIG changes it
        LDMIA   R8, {R6, R7}            ; and we don't want it to!

; *****Change made by DJS
; Original code was:
;        LDRB    R0, [WsPtr, #QQ+5]      ; rightHi
;        LDRB    R1, [WsPtr, #QQ+4]      ; rightLo
;        PackXtnd R0,R0,R1               ; pack 2 bytes and sign extend
;
;        LDRB    R1, [WsPtr, #QQ+7]      ; topHi
;        LDRB    R2, [WsPtr, #QQ+6]      ; topLo
;        PackXtnd R1,R1,R2               ; pack 2 bytes and sign extend

        LoadCoordPair R0, R1, WsPtr, QQ+4 ;Get top right point

; *****End of change made by DJS

        MOV     R2, #&FF                ; convert external-to-internal
        BL      EIG                     ; as absolute coordinates

        MOV     R4, R0                  ; move internal version of top right
        MOV     R5, R1                  ; out of harm's way

; *****Change made by DJS
; Original code was:
;        LDRB    R0, [WsPtr, #QQ+1]      ; leftHi
;        LDRB    R1, [WsPtr, #QQ+0]      ; leftLo
;        PackXtnd R0,R0,R1               ; pack 2 bytes and sign extend
;
;        LDRB    R1, [WsPtr, #QQ+3]      ; bottomHi
;        LDRB    R2, [WsPtr, #QQ+2]      ; bottomLo
;        PackXtnd R1,R1,R2               ; pack 2 bytes and sign extend

        LoadCoordPair R0, R1, WsPtr, QQ+0 ;Get bottom left point

; *****End of change made by DJS

        MOV     R2, #&FF                ; convert external-to-internal
        BL      EIG                     ; as absolute coordinates

; For a valid window, the following must be true

        CMP     R4, R0                          ;  RCol >= LCol
        CMPGE   R5, R1                          ;  TRow >= BRow
        CMPGE   R0, #0                          ;  LCol >= 0
        CMPGE   R1, #0                          ;  BRow >= 0
        LDRGE   R2, [WsPtr, #YWindLimit]        ;  YWindLimit >= TRow
        CMPGE   R2, R5
        LDRGE   R2, [WsPtr, #XWindLimit]        ;  XWindLimit >= RCol
        CMPGE   R2, R4

        ADD     R2, WsPtr, #GWLCol
        STMGEIA R2, {R0,R1, R4,R5}      ; if the new window is OK, update it

        STMIA   R8, {R6, R7}            ; restore ECursor (EIG corrupted it)
        Pull    PC

; *****************************************************************************
;
;       DefaultWindows - Restore default windows
;
;       External routine, and called by mode change + switch output to sprite
;
;       Set default text and graphics windows,
;       Clear graphics origin and both cursors
;

DefaultWindows ROUT
        Push    R14
        MOV     R0, #0
        MOV     R1, #0
        ADD     R4, WsPtr, #GWLCol

        ASSERT  YWindLimit = XWindLimit +4

        ADD     R2, WsPtr, #XWindLimit
        LDMIA   R2, {R2,R3}     ; R2 := XWindLimit; R3 := YWindLimit
        STMIA   R4, {R0-R3}     ; zero GWLCol, GWBRow
                                ; GWRCol:=XWindLimit; GWTRow:=YWindLimit
        MOV     R3, #0
        LDR     R1, [WsPtr, #ScrBRow]
        LDR     R2, [WsPtr, #ScrRCol]
        ADD     R4, WsPtr, #TWLCol      ; zero TWLCol, TWTRow
        STMIA   R4!, {R0-R3}            ; TWRCol := ScrRCol; TWBRow := ScrBRow

        MOV     R1, #0
        MOV     R2, #0
        STMIA   R4!, {R0-R3}    ; zero OrgX, OrgY, GCsX, GCsY
        STMIA   R4!, {R0-R3}    ; zero OlderCsX, OlderCsY, OldCsX, OldCsY
        STMIA   R4!, {R0-R3}    ; zero GCsIX, GCsIY, NewPtX, NewPtY

        LDR     R0, [WsPtr, #VduSprite]
        TEQ     R0, #0

        LDR     R0, [WsPtr, #VduStatus]         ; if not outputting to sprite
        BICEQ   R0, R0, #Windowing              ; then indicate no text window
        ORRNE   R0, R0, #Windowing              ; else indicate is text window
        STR     R0, [WsPtr, #VduStatus]

        BL      HomeVdu4                        ; home TEXT cursor
                                                ; (even in VDU 5 mode)
        Pull    PC

; *****************************************************************************
;
;       GS - Define graphics origin
;
;       External routine
;
; in:   The origin is given by bytes in the vdu queue, as follows :-
;         QQ+0 = xLo
;         QQ+1 = xHi
;         QQ+2 = yLo
;         QQ+3 = yHi
;
;       The coordinates are in external 16 bit form.
;       This does not move the windows, but does move the graphics cursor
;

GS      ROUT
        Push    R14

; *****Change made by DJS
; Original code was:
;        LDRB    R0, [WsPtr, #QQ+1]      ; xHi
;        LDRB    R1, [WsPtr, #QQ+0]      ; xLo
;        PackXtnd R0,R0,R1               ; pack 2 bytes and sign extend
;        LDRB    R1, [WsPtr, #QQ+3]      ; yHi
;        LDRB    R2, [WsPtr, #QQ+2]      ; yLo
;        PackXtnd R1,R1,R2               ; pack 2 bytes and sign extend

        LoadCoordPair R0, R1, WsPtr, QQ+0

; *****End of change made by DJS

        ADD     R2, WsPtr, #OrgX
        STMIA   R2, {R0,R1}             ; write the new origin
        BL      IEG                     ; update external cursor
        Pull    PC

        LTORG

;------------------------------------------------------------------------------
;
; OS_SetColour implementation
; ------------
;
; This call can be used to change the current GCOL/pattern table used for
; plotting with the VDU primitives.
;
; in    R0 = flags / logical operation
;               bit 0-3 = logical operation
;               bit 4   = set => bg, else fg flag
;               bit 5   = set => pattern block supplied
;               bit 6   = set => set text colour
;               bit 7   = set => read colour
;       R1 = colour number / -> pattern block to use
; out   -
;
;------------------------------------------------------------------------------

setcol_LogicOpMask      * &0F
setcol_FgBgFlag         * &10
setcol_PatternFlag      * &20
setcol_TextColour       * &40
setcol_ReadFlag         * &80

        ASSERT  WsPtr > 9

SWISetColour ROUT

        Push    "R0-R9,WsPtr,R14"

        VDWS    WsPtr                           ; Obtain base of the VDU driver workspace

        TST     R0, #setcol_ReadFlag            ; Are we reading?
        BNE     %FT75

        TST     R0, #setcol_TextColour          ; Are we changing the text colour?
        BNE     %FT70

        AND     R2, R0, #setcol_LogicOpMask     ; Get the logical operation
        ORR     R2, R2, #&60                    ; Mark as being a special kind of pattern

        TST     R0, #setcol_FgBgFlag
        STREQ   R2, [WsPtr, #GPLFMD]            ; Store the relevant logical operation away for fg/bg
        STRNE   R2, [WsPtr, #GPLBMD]
        ADDEQ   R3, WsPtr, #FgPattern
        ADDNE   R3, WsPtr, #BgPattern           ; Setup the pointer to a store for the pattern

        TST     R0, #setcol_PatternFlag         ; Did the caller specify a pattern block?
        BNE     %FT50                           ; Yes so don't try to expand colour value to pattern

        MOV     R2, #1
        LDR     R4, [WsPtr, #Log2BPP]           ; Get the Log2 depth of the mode
        MOV     R4, R2, ASL R4                  ; R4 = bits per pixel
        RSB     R2, R2, R2, LSL R4              ; Get a mask to extract only meaningful bits from word
        AND     R1, R1, R2                      ; Extract bits suitable for this depth of mode
10
        TEQ     R4, #32                         ; Do we need to do any more replication
        ORRNE   R1, R1, R1, LSL R4              ; Yes so or word with itself shifted
        MOVNE   R4, R4, LSL #1                  ; and double amount to shift next time
        BNE     %BT10

        MOV     R2, #8
20
        STR     R1, [R3], #4                    ; now copy word 8 times into block
        SUBS    R2, R2, #1
        BNE     %BT20
        B       %FT60

50
        LDMIA   R1,{R0,R2,R4-R9}
        STMIA   R3,{R0,R2,R4-R9}                ; Copy the pattern into the buffer (assumes word aligned)
60
        BL      SetColour                       ; And then setup the internal GCOL tables
65
        Pull    "R0-R9,WsPtr,R14"
        ExitSWIHandler
70
        TST     R0, #setcol_FgBgFlag            ; Store the foreground or background colour?
        STREQ   R1, [WsPtr, #TextFgColour]
        STRNE   R1, [WsPtr, #TextBgColour]

        LDR     R0, [WsPtr, #CursorFlags]       ; Indicate the text colour needs re-computing!
        ORR     R0, R0, #TEUpdate
        STR     R0, [WsPtr, #CursorFlags]

        B       %BT65                           ; Leave gracefully ....

75
        ; Reading the colour...
        TST     R0, #setcol_TextColour
        BEQ     %FT80

        ; Reading text colour
        TST     R0, #setcol_FgBgFlag
        LDREQ   R1, [WsPtr, #TextFgColour]
        LDRNE   R1, [WsPtr, #TextBgColour]
        BIC     R0, R0, #setcol_PatternFlag :OR: setcol_ReadFlag

77
        ; Standard exit for reading the colour
        STMIA   SP, {R0,R1}
        B       %BT65

80
        ; Reading graphics colour
        TST     R0, #setcol_FgBgFlag
        LDREQ   R2, [WsPtr, #GPLFMD]            ; Get the relevant logical operation for fg/bg
        LDRNE   R2, [WsPtr, #GPLBMD]

        ; SetColour setting - copy block
        ADDEQ   R3, WsPtr, #FgEcf
        ADDNE   R3, WsPtr, #BgEcf

        ; Copy the pattern to the user's buffer
        LDMIA   R3,{R0,R3,R4-R9}
        STMIA   R1,{R0,R3,R4-R9}

        ; Construct a suitable reason code
        AND     R0, R2, #setcol_LogicOpMask
        ORRNE   R0, R0, #setcol_FgBgFlag
        ORR     R0, R0, #setcol_PatternFlag
        B       %BT77

; *****************************************************************************
;
;       HandleServiceDisplayStatus
;
;       Called from kernel service call handler
;
; in:   R0 = sub-reason
;       R1 = Service_DisplayStatus
;       R2 = GraphicsV driver number
;       LR = return address
;
; out:  R12 corrupt
;
HandleServiceDisplayStatus ROUT
        ; We're only interested in DisplayStatus_Changing/DisplayStatus_Changed
        TEQ     r0, #DisplayStatus_Changing
        TEQNE   r0, #DisplayStatus_Changed
        MOVNE   pc, lr
        Entry   "r0-r4"

        ; We're only interested in the current driver
        VDWS    WsPtr
        LDR     r1, [WsPtr, #CurrentGraphicsVDriver]
        TEQ     r1, r2
        BNE     %FT90

        ; Translate this call into the corresponding Service_DisplayChanging calls
        ; DisplayStatus_Changing -> DisplayChanged_PreChanging
        ; DisplayStatus_Changed  -> DisplayChanged_Changing,
        ;                           DisplayChanged_Changed
        ; This may sound odd, but that's how it fits with the way the
        ; DisplayChanged service call is designed. The ScreenModes module will
        ; recache its mode list when it receives DisplayChanged_Changing, so we
        ; must only issue the call once the driver has finished changing its
        ; configuration.

        TEQ     r0, #DisplayStatus_Changed
        BEQ     %FT50
        MOV     r0, #DisplayChanged_PreChanging
        MOV     r1, #Service_DisplayChanged
        MOV     r3, #DisplayChangedSub_ModeNotChanged
        IssueService
        EXIT

50
        ; It was a DisplayStatus_Changed call. Recache any important values
        ; that we care about.
        MOV     r4, r2, LSL #24
        ORR     r4, r4, #GraphicsV_DisplayFeatures
        BL      CallGraphicsV
        STR     r0, [WsPtr, #GraphicsVFeatures]
        BL      UpdateFalseVsync

        ; Currently we assume that the driver's hardware
        ; scroll ability won't have been changed.

        ; Now issue the two DisplayChanged service calls
        MOV     r0, #DisplayChanged_Changing
        MOV     r1, #Service_DisplayChanged
        FRAMLDR r2
        MOV     r3, #DisplayChangedSub_ModeNotChanged
        IssueService
        MOV     r0, #DisplayChanged_Changed
        IssueService
90
        EXIT


        LTORG

        END