Direct vesa:Fastest way to use the mouse?

DOS specific questions.
Post Reply
lassar
Posts: 306
Joined: Jan 17, 2006 1:35

Direct vesa:Fastest way to use the mouse?

Post by lassar »

What is the fastest way to use the mouse in direct vesa mode. (No freebasic fbgfx graphic commands)

Do using interrupts, going from protected mode to real mode and back, slow things down?
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Re: Direct vesa:Fastest way to use the mouse?

Post by MichaelW »

For real-mode DOS and QuickBASIC one method that worked well for me was to set up a mouse driver interrupt sub that stored the mouse status in a QuickBASIC global structure, eliminating the need to periodically call the mouse driver. Once installed, the mouse driver called the interrupt sub for any of the defined conditions (events), the interrupt sub updated the QuickBASIC global structure, and the QuickBASIC app could simply read the mouse status from the structure members. Here are the relavant parts of the assembly module source:

Code: Select all

;===================================================
; This is the MASM 6+ source code for a QuickBASIC
; Mouse procedure library. Beyond providing wrappers
; for a few of the most common mouse functions, it
; includes a MouseInit procedure that initializes
; the mouse driver and installs a mouse driver
; interrupt subroutine that maintains the current
; mouse status in a QuickBASIC global variable.
;
; The QuickBASIC global variable must be of type
; MouseType, and the segment and offset addresses
; of the variable must be passed in the first call
; to the MouseInit procedure. The button states
; are TRUE (-1) if the button is pressed, or FALSE
; (0) if the button is not pressed. The event mask
; indicates what type of mouse event triggered the
; most recent interrupt. The possible events are a
; position change or a press or release of the
; left or right button.
;
; TYPE MouseType
;  x        AS INTEGER ' Cursor X coordinate
;  y        AS INTEGER ' Cursor Y coordinate
;  left     AS INTEGER ' Left button state
;  right    AS INTEGER ' Right button state
;  event    AS INTEGER ' Event mask
; END TYPE
; DIM SHARED mouse AS MouseType
;
; These constants are used to interpret the event
; mask. The assigned values are the value of the
; corresponding bit in the event mask, so an
; event can be detected by ANDing the appropriate
; constant with the event mask.
; CONST POSITION = 1
; CONST LPRESS = 2
; CONST LRELEASE = 4
; CONST RPRESS = 8
; CONST RRELEASE = 16
;
; For the range of video modes that are supported
; by QuickBASIC, the interrupt subroutine and the
; MouseSetPosition procedure automatically
; translate between mouse driver virtual screen
; coordinates and physical screen coordinates,
; so as viewed from the QuickBASIC module all
; coordinates are physical screen coordinates.
; For the text modes, the x coordinate will be
; the base 1 column position and the y coordinate
; will be the base 1 row position.
;
; For uniformity, all of the procedures that take
; arguments expect the arguments to be passed by
; reference. "Pass by reference" is the QB default
; and it means that the value passed is the address
; of the argument in the QB default data segment.
; Within the procedures, the arguments, which as
; viewed from the procedure are properly termed
; "parameters", are accessed by first loading the
; address of the parameter into a base or index
; register and then by using a register indirect
; form of the instruction that accesses the
; parameter. For example, to load the value of a
; parameter named varPtr into AX:
;
;     mov   bx,varPtr
;     mov   ax,[bx]
;
; QB procedures are always called with a far call
; and the procedures must preserve the direction
; flag and the BP, DI, SI, DS, and SS registers.
; The BASIC calling conventions require that the
; arguments be pushed onto the stack in left to
; right order as they appear in the procedure
; definition, and that the called procedure remove
; the arguments from the stack. One significant
; advantage of using MASM for mixed language
; programming is that you can specify a language
; type in the .MODEL directive and MASM will
; automatically generate the code that is required
; to properly implement the calling conventions.
; For example, for the RET instruction in the
; procedures MASM knows to encode a far return,
; and for the procedures that take arguments, MASM
; knows to add an operand to the RET instruction
; that will cause the processor to add an
; appropriate value to SP after the procedure has
; returned to the caller.
;===================================================

; Declare a structure to use as a template when
; accessing the mouse status variable.

MouseType struct
   x        WORD ?
   y        WORD ?
   left     WORD ?
   right    WORD ?
   event    WORD ?
MouseType ends

; This prototype establishes the call interface for
; the B_OnExit procedure (so MASM will know how to
; call it) and effectively generates an external
; declaration in this module (so MASM will know that
; the procedure is defined in another module).

B_OnExit proto far basic :far ptr

; This is the normal model specification for QB.

.model medium,basic

; Enable assembly of the 186 instruction set (the
; minimum processor that will support an immediate
; (constant) operand for a push instruction).

.186

; Start a near data segment. The linker will combine
; this segment with the QB default data segment.

.data
   ; Allocate a flag variable to lock out multiple
   ; calls to MouseInit. The name avoids the MASM
   ; reserved word "finit".

   f_init   dw 0

   ; Define screen and cursor masks for a crosshair
   ; cursor. The cursor defintion is within this
   ; module rather than being passed as a parameter
   ; simply because defining the cursor with binary
   ; numbers is much easier than defining it with
   ; hex numbers.
   ;
   ; Note that this cursor will not work correctly
   ; for SCREEN 1, and that the aspect ratio will
   ; be significantly off for the 640x200 modes. 
   ;
   ; For the QB graphics modes other than SCREEN 1,
   ; the mouse driver will AND the screen mask bits
   ; with the corresponding screen pixel bits and
   ; XOR the result with the cursor mask. In truth
   ; table form:
   ;
   ;  screen mask cursor mask resulting screen bit
   ;       0            0              0
   ;       0            1              1
   ;       1            0          unchanged
   ;       1            1          inverted
   ;
   ; Note that the masks bits are expanded as
   ; necessary for the current graphics mode.
   ; For example, for mode 13h (SCREEN 13) each
   ; mask bit is expanded to 8 bits and these
   ; bits are then combined with the 8 attribute
   ; bits for the corresponding screen pixel.

              ;0123456701234567
   cross    dw 1111111111111111b ;0
            dw 1111111111111111b ;1
            dw 1111111011111111b ;2
            dw 1111111011111111b ;3
            dw 1111111011111111b ;4
            dw 1111111011111111b ;5
            dw 1111111111111111b ;6
            dw 0000001110000001b ;7
            dw 1111111111111111b ;0
            dw 1111111011111111b ;1
            dw 1111111011111111b ;2
            dw 1111111011111111b ;3
            dw 1111111011111111b ;4
            dw 1111111111111111b ;5
            dw 1111111111111111b ;6
            dw 1111111111111111b ;7

            dw 0000000000000000b ;0
            dw 0000000000000000b ;1
            dw 0000000100000000b ;2
            dw 0000000100000000b ;3
            dw 0000000100000000b ;4
            dw 0000000100000000b ;5
            dw 0000000000000000b ;6
            dw 1111110001111110b ;7
            dw 0000000000000000b ;0
            dw 0000000100000000b ;1
            dw 0000000100000000b ;2
            dw 0000000100000000b ;3
            dw 0000000100000000b ;4
            dw 0000000000000000b ;5
            dw 0000000000000000b ;6
            dw 0000000000000000b ;7

; Start a (far) code segment.

.code

; This include directive includes TEST.ASM in this
; file, effectively making it part of this file.

include test.asm

;===================================================

; Allocate a variable to store the far address of
; the mouse status variable, and a lookup table of
; shift and base adjust values indexed by BIOS video
; mode, that will be used to translate between mouse
; driver virtual screen coordinates and physical
; screen coordinates. These variables need to be in
; the code segment because they must be accessed
; from the interrupt subroutine, and when the mouse
; driver calls the subroutine the only segment
; register with a known value is CS.

statusVarPtr   dd 0

; To allow the table to be indexed by the BIOS video
; mode the table must include all mode numbers from
; 0 to 13h, even though several of the included
; modes have no corresponding QB SCREEN mode and
; others are not supported on the typical PC. Each
; element consists of an x shift word, a y shift
; word, a base adjust word, and a pad word that is
; included to simplify table indexing. The base
; adjust value is 1 for the text modes and 0 for
; the graphic modes. Aligning the table on a word
; boundary minimizes the time required to access
; the table.

align 2
luTable  dw 4,3,1,0  ; 0
         dw 4,3,1,0  ; 1 (SCREEN 0, 40 column)
         dw 3,3,1,0  ; 2
         dw 3,3,1,0  ; 3 (SCREEN 0, 80 column)
         dw 1,0,0,0  ; 4 (SCREEN 1)
         dw 1,0,0,0  ; 5
         dw 0,0,0,0  ; 6 (SCREEN 2)
         dw 3,3,1,0  ; 7 (SCREEN 0, monochrome only)
         dw 2,0,0,0  ; 8 PCjr only
         dw 0,0,0,0  ; 9 PCjr only
         dw 0,0,0,0  ; A PCjr only
         dw 0,0,0,0  ; B EGA BIOS internal only
         dw 0,0,0,0  ; C EGA BIOS internal only
         dw 1,0,0,0  ; D (SCREEN 7)
         dw 0,0,0,0  ; E (SCREEN 8)  
         dw 0,0,0,0  ; F (SCREEN 10) 
         dw 0,0,0,0  ; 10 (SCREEN 9)
         dw 0,0,0,0  ; 11 (SCREEN 11)
         dw 0,0,0,0  ; 12 (SCREEN 12)
         dw 1,0,0,0  ; 13 (SCREEN 13)
;===================================================

; The following procedure declarations use the
; distance (far) and langtype (basic) from the
; .MODEL directive, and default to PUBLIC
; visibility.

;===================================================
; This proc calls the mouse driver Mouse Reset and
; Status function to reset the mouse driver to clear
; the interrupt subroutine call mask, which causes
; the mouse driver to cease calling the interrupt
; subroutine.

; This declaration must be placed before the
; reference to it in the MouseInit procedure.
;===================================================
TermProc proc
      xor   ax,ax
      int   33h
      ret
TermProc endp

;===================================================
; This proc checks for a mouse driver, resets it,
; saves the segment and offset addresses of the
; global status variable, installs the interrupt
; subroutine, and calls the B_OnExit routine to log
; a termination procedure that will automatically
; disable the interrupt subroutine when the program
; terminates. It returns non-zero for success, or
; zero for failure. The first call sets a flag that
; locks out subsequent calls.
;===================================================
MouseInit proc varSeg:WORD,varPtr:WORD

      ; Lock out subseqent calls.
      .IF f_init != 0
         xor   ax,ax
         ret
      .ENDIF
      mov   f_init,-1

      ; Check for a mouse driver.

      ; Use the DOS Get Interrupt Vector function
      ; to get the interrupt 33h vector. If the
      ; segment address is zero, then the mouse
      ; driver is not installed.
      mov   ax,3533h
      int   21h
      mov   ax,es
      .IF ax == 0
         ret
      .ENDIF

      ; Attempt to reset the mouse driver. If the
      ; reset fails then the mouse driver is not
      ; installed.
      xor   ax,ax
      int   33h
      .IF ax == 0
         ret
      .ENDIF

      ; Save the address of the mouse status variable.
      mov   bx,varPtr
      mov   ax,[bx]
      mov   word ptr cs:statusVarPtr,ax
      mov   bx,varSeg
      mov   ax,[bx]
      mov   word ptr cs:statusVarPtr+2,ax

      ; Install the interrupt subroutine.
      mov   ax,12
      ; This condition mask specifies that an
      ; interrupt be generated for any of the
      ; defined conditions.
      mov   cx,11111b
      push  cs
      pop   es
      mov   dx,OFFSET InterruptSub
      int   33h

      ; Register the termination routine.

      ; The arguments must be pushed onto the stack
      ; as per the basic calling convention before
      ; calling the routine.
      push  cs
      push  OFFSET TermProc
      call  B_OnExit

      ; Return whatever B_OnExit returned.
      ret
MouseInit endp

;===================================================
; This proc is the interrupt subroutine. The mouse
; driver will call it for each mouse interrupt.
;
; The local variables allow temporary values to be
; stored on the stack in named locations.
;===================================================
align 2
InterruptSub proc

      LOCAL xShift:WORD,yShift:WORD,baseAdjust:WORD

      ; Preserve DS before changing it because the
      ; mouse driver probably will not expect it to
      ; change.
      push  ds

      ; Preserve BX before changing it because it
      ; contains the button status.
      push  bx

      ; Get the video mode from the BIOS data area.
      mov   bx,40h
      mov   ds,bx
      mov   bx,49h
      mov   bx,[bx]
      ; Because the mode is actually stored as a
      ; byte and we are reading a word, and because
      ; bit 7 of the byte may or may not be set
      ; depending on whether or not the display
      ; memory was erased during the most recent
      ; mode set, we need to discard the upper 9
      ; bits of BX.
      and   bx,1111111b

      ; Get the corresponding x and y shift and
      ; base adjust values and store them in
      ; local variables. BX must first be scaled by
      ; the size of the table elements (BX=BX*8).
      shl   bx,3
      ; For instructions that take two operands,
      ; only one can be a memory operand. The push-
      ; pop sequences are used to perform a memory
      ; to memory move without involving a scratch
      ; register. Local variables can be accessed
      ; without a segment override because they are
      ; allocated from the stack, so the POP
      ; instructions reference BP (for example,
      ; "pop xShift" is encoded as "pop [bp-2]"),
      ; so the processor automatically uses SS.
      push  cs:[luTable+bx]
      pop   xShift
      push  cs:[luTable+bx+2]
      pop   yShift
      push  cs:[luTable+bx+4]
      pop   baseAdjust

      ; Load the status variable address into DS:BX.      
      mov   bx,word ptr cs:[statusVarPtr]
      mov   ds,word ptr cs:[statusVarPtr+2]

      ; This assume informs MASM that BX contains
      ; a pointer to a variable of MouseType, so
      ; it will know what the following references
      ; to [bx].elementname mean:
      ASSUME bx:ptr MouseType

      ; Store the event mask in the status variable.
      mov   [bx].event,ax

      ; We are Finished with the value in AX so pop
      ; the preserved button status into it.
      pop   ax

      ; Store the button status in the status
      ; variable. Bit0 of the button status will
      ; be set if the left mouse button is pressed
      ; and bit1 if the right mouse button is
      ; pressed. In the MASM 6+ High Level syntax
      ; "&" is the bit test operator, which returns
      ; true if the bit with the specified "place"
      ; value is set.
      .IF ax & 1
         mov   [bx].left,-1
      .ELSE
         mov   [bx].left,0
      .ENDIF
      .IF ax & 2
         mov   [bx].right,-1
      .ELSE
         mov   [bx].right,0
      .ENDIF

      ; Convert the virtual screen coordinates
      ; of the mouse cursor to physical screen
      ; coordinates and store the results in
      ; the status variable. 
      mov   ax,cx
      mov   cx,xShift
      shr   ax,cl
      add   ax,baseAdjust
      mov   [bx].x,ax

      mov   ax,dx
      mov   cx,yShift
      shr   ax,cl
      add   ax,baseAdjust
      mov   [bx].y,ax

      ; Remove the assumption for BX.
      ASSUME bx:NOTHING

      ; Recover DS.
      pop   ds

      ret
InterruptSub endp
Snip…
The missing file test.asm is not necessary for your purposes, but by way of explaining what it was here is the start of the header:

Code: Select all

; This file contains a procedure that performs a
; measurement of the number of clock cycles the
; interrupt subroutine takes to execute. To use the
; procedure, include this file in MOUSELIB.ASM
;
; The purpose of the measurement was to ensure that,
; in the worst case, the interrupt subroutine would
; not consume an unreasonable amount of processor
; time.
I have had only a few minutes to consider this, but I think you should be able to adapt this method to work with FreeBASIC DOS. Specifically, I think you could use the Allocate Real Mode Call-Back Address function to handle the interrupt sub call in protected mode. I have no idea how to translate the mouse driver virtual screen coordinates to screen coordinates for non-VGA modes.
lassar
Posts: 306
Joined: Jan 17, 2006 1:35

Re: Direct vesa:Fastest way to use the mouse?

Post by lassar »

How do you use the Allocate Real Mode Call-Back Address function to call a protected mode subroutine?
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Re: Direct vesa:Fastest way to use the mouse?

Post by MichaelW »

Do using interrupts, going from protected mode to real mode and back, slow things down?
Yes, there is a surprising amount of overhead involved.

This is a FB-DOS version of the newer cycle count macros (counter.bas):

Code: Select all

''=============================================================================
dim shared as longint counter_cycles
dim shared as integer _counter_loopcount_, _counter_loopcounter_

#macro COUNTER_BEGIN( loop_count )
    _counter_loopcount_ = loop_count
    _counter_loopcounter_ = _counter_loopcount_
    asm
        xor eax, eax
        cpuid               '' serialize
        rdtsc               '' get reference loop start count
        push edx            '' preserve msd (most significant dword)
        push eax            '' preserve lsd
        xor eax, eax
        cpuid               '' serialize
        .balign 16
      0:                    '' start of reference loop
        sub DWORD PTR _counter_loopcounter_, 1
        jnz 0b              '' end of reference loop
        xor eax, eax
        cpuid               '' serialize
        rdtsc               '' get reference loop end count
        pop ecx             '' recover lsd of start count
        sub eax, ecx        '' calc lsd of reference loop count
        pop ecx             '' recover msd of start count
        sbb edx, ecx        '' calc msd of reference loop count
        push edx            '' preserve msd of reference loop count
        push eax            '' preserve lsd of reference loop count
        xor eax, eax
        cpuid               '' serialize
        rdtsc               '' get test loop start count
        push edx            '' preserve msd
        push eax            '' preserve lsd
    end asm
    _counter_loopcounter_ = _counter_loopcount_
    asm
        xor eax, eax
        cpuid               '' serialize
        .balign 16
      1:                    '' start of test loop
    end asm
#endmacro

''=============================================================================

#macro COUNTER_END()
    asm
        sub DWORD PTR _counter_loopcounter_, 1
        jnz 1b              '' end of test loop
        xor eax, eax
        cpuid               '' serialize
        rdtsc
        pop ecx             '' recover lsd of start count
        sub eax, ecx        '' calc lsd of test loop count
        pop ecx             '' recover msd of start count
        sbb edx, ecx        '' calc msd of test loop count
        pop ecx             '' recover lsd of reference loop count
        sub eax, ecx        '' calc lsd of corrected loop count
        pop ecx             '' recover msd of reference loop count
        sbb edx, ecx        '' calc msd of corrected loop count
        mov DWORD PTR [counter_cycles], eax
        mov DWORD PTR [counter_cycles+4], edx
    end asm
    counter_cycles /= _counter_loopcount_
#endmacro

''=============================================================================
To get an accurate value for the overhead of a RM software interrupt called from PM we would ideally need an interrupt where the handler consisted of an IRET instruction, so it would simply return from the interrupt. While this is doable, in the interest of keeping it simple the code below calls an interrupt where the handler simply reads a word from the BIOS data area and returns it in AX. The code for the handler should look something like this:

Code: Select all

    jmp handler
    ...
  handler:  
    push ds
    mov ds, cs:[xxxx]
    mov ax, ds:[0010]
    pop ds
    iret
And excluding the IRET it should execute in some small number of clock cycles.

Code: Select all

#include "counter.bas"

sleep 5000

''-----------------------------------------------------------
'' Disable the maskable interrupts so the system timer tick,
'' keyboard interrupt, etc will not interfere with the cycle
'' count code.
''-----------------------------------------------------------

asm cli

for i as integer = 1 to 6

    COUNTER_BEGIN( 100 )
    COUNTER_END()
    print counter_cycles

    COUNTER_BEGIN( 100 )
        asm int 0x11
    COUNTER_END()
    print counter_cycles

next

asm sti

sleep
Results running on a P2 under MS-DOS 6.22 with HDPMI and CWSDPMI:

Code: Select all

HDPMI:
0
2112
0
2110
0
2109
0
2110
0
2110
0
2110
CWSDPMI:
0
4665
0
4653
0
4667
0
4673
0
4672
0
4651
The results running on the same system under Window ME were very close to the HDPMI results.

Results with the Windows XP NTVDM:

Code: Select all

-1
 12768
 0
 12653
 0
 12688
 0
 12738
 0
 12645
 0
 12678
The method that I suggested above would eliminate the need for all but a few of the software interrupts, but as with most hardware devices there would still be hardware interrupts going on, and it is the mouse hardware interrupts that trigger the calls to the mouse driver interrupt sub. All of the mice that I have tested were generating hardware interrupts at a rate of 200 per second. IIRC the maximum rate is 400 interrupts per second. When polling the mouse driver with software interrupts, depending on the structure of your program loop, you could be generating many thousands of interrupts per second.
How do you use the Allocate Real Mode Call-Back Address function to call a protected mode subroutine?
I have not tried yet, but I expect that it will not be difficult. Which VESA modes do you expect to be using?
lassar
Posts: 306
Joined: Jan 17, 2006 1:35

Re: Direct vesa:Fastest way to use the mouse?

Post by lassar »

Vesa LBF mode 101h, 640x480 8 bit color.
Post Reply