News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Double buffer in mode 13H

Started by popcalent, February 07, 2024, 04:46:28 PM

Previous topic - Next topic

popcalent

I created the following procedure to uninstall my handler. It seems to work, but I thought I would post it here for others to see/use. Also, it's possible the handler has errors that didn't manifest during my tests.
UNSET_INT15H PROC NEAR
        PUSH    DX
        PUSH    ES                      ;Save segment reg.
        XOR    AX, AX                  ;Point to "zero segment".
        MOV    ES, AX
        MOV    BX, 15h * 4            ;Address of INT 15h vector.

        CLI                            ;Disable interrupts.
        MOV    AX, OLD_INT15_OFF      ; Restore original offset.
        MOV    ES:[BX], AX
        MOV    AX, OLD_INT15_SEG      ; Restore original segment.
        MOV    ES:[BX + 2], AX
        STI                            ;Re-enable interrupts.

        POP    ES                      ;Restore segment reg.
        POP    DX
        RET
        UNSET_INT15H ENDP

Just for convenience, here's the SET_INT15H procedure again:
SET_INT15H PROC NEAR
        PUSH    DX
        PUSH    ES                      ;Save segment reg.
        XOR    AX, AX                  ;Point to "zero segment".
        MOV    ES, AX
        MOV    BX, 15h * 4            ;Address of INT 15h vector.
        MOV    AX, ES:[BX]            ;Get offset.
        MOV    OLD_INT15_OFF, AX
        MOV    AX, ES:[BX + 2]        ;Get segment.
        MOV    OLD_INT15_SEG, AX

        CLI                            ;Disable interrupts.
        MOV    AX, OFFSET INT_15H_HANDLER
        MOV    ES:[BX], AX            ;Plug in new offset.
        MOV    AX, CS                  ;Get our code segment.
        MOV    ES:[BX + 2], AX        ;Plug it in.
        STI                            ;Re-enable interrupts.
        POP    ES                      ;Restore segment reg.
        POP    DX
        RET
        SET_INT15H ENDP

This is the handler code:
INT_15H_HANDLER PROC NEAR
        CMP    AH, 4Fh
        JNE    EXIT_HANDLER

        PUSH    ES
        MOV    DX, DATASG
        MOV    ES, DX
        MOV    ES:PRESSED_KEY, AL
        POP    ES

        EXIT_HANDLER:
                JMP    CS:[OLD_INT15_VECTOR]
        RET
        INT_15H_HANDLER ENDP

The handler puts the scan code of the pressed key in PRESSED_KEY, and then I take care of it in the main loop. The main loop sees the scan code and pushes a direction (R, L, U, D) into the buffer if the corresponding arrow key has been pressed, or pops a direction from the buffer if the corresponding arrow key has been released. The sprite moves in the direction stored in the first position of the buffer, or doesn't move if the buffer is empty.

Now, I know we've mentioned that the handler has to be as short as possible, but it occurred to me that if push/pop stuff into/out of the buffer outside the handler, it's possible that a key is pressed before the current key stroke is taken care of in the main loop. The only way to avoid this that I can think of is to manipulate the buffer inside the handler, but the code is pretty long for a handler... I'm not sure what to do. 

popcalent

Quote from: NoCforMe on February 09, 2024, 10:40:34 AMWhy don't you try my suggestion and see what it does for you? (Including inlining your PUT_PIXEL_IN_BUFFER code.)

I finally got around fixing this issue. Here it is...
PRINT_256SPRITE PROC NEAR
        PUSH    AX
        PUSH    BX
        PUSH    CX
        PUSH    DX
        PUSH    ES
        PUSH    DI
        PUSH    DS
        PUSH    SI

        MOV     CX, MODE13H_X  ;XCOORD
        MOV     DX, CX
        MOV     BX, MODE13H_Y ;YCOORD

        ;PREPARE BUFFER AND BUFFER INDEX (SI)
        MOV     AX, SEG SBUFFER
        MOV     ES, AX
        ASSUME  ES:SBUFFER
        ;MOV     SI, OFFSET SCREEN_BUFFER

        ;FIND ABSOLUTE COORDINATE OF FIRST PIXEL
        MOV     AX, MODE13H_Y
        MOV     BX, MODE13H_Y
        SHL     AX, 8
        SHL     BX, 6
        ADD     AX, BX
        ADD     AX, MODE13H_X
        MOV     MODE13H_X, AX
        MOV     MODE13H_X2, AX          ;MODE13H_X2 HAS THE ORIGINAL ABSOLUTE COORD

        PRINT_256SPRITE_LOOP:
                CMP     BYTE PTR [DI],'$'       ;CHECK END OF SPRITE
                JE      PRINT_256SPRITE_END

                ;PREPARE BUFFER AND BUFFER INDEX (SI)
                ;MOV     AX, SEG SBUFFER
                ;MOV     ES, AX
                ;ASSUME  ES:SBUFFER
                MOV     SI, OFFSET SCREEN_BUFFER

                MOV     AX, MODE13H_X
                ADD     SI, AX
                MOV     AL, BYTE PTR [DI]       ;GET PIXEL COLOR
                MOV     ES:[SI], AL             ;PUT PIXEL COLOR

                INC     DI
                INC     MODE13H_X
                CMP     BYTE PTR [DI], '#'      ;CHECK END OF LINE
                JNE     PRINT_256SPRITE_LOOP

                MOV     AX, MODE13H_X2  ;RECOVER ABSOLUTE COORDINATE
                ADD     AX, 140h        ;ADD 320 TO GO TO NEXT LINE SAME X
                MOV     MODE13H_X2, AX  ;SAVE NEW ABSOLUTE COORDINATE IN X2
                MOV     MODE13H_X, AX   ;SAVE NEW ABSOLUTE COORDINATE IN X
                ;INC     MODE13H_Y
                INC     DI              ;SKIP '#'
                JMP     PRINT_256SPRITE_LOOP

        PRINT_256SPRITE_END:
                POP     SI
                POP     DS
                POP     DI
                POP     ES
                POP     DX
                POP     CX
                POP     BX
                POP     AX
        RET
PRINT_256SPRITE ENDP

I still need to figure out this part:

MOV     AL, BYTE PTR [DI]       ;GET PIXEL COLOR
MOV     ES:[SI], AL             ;PUT PIXEL COLOR

and see if I can do MOVSW because I'm not sure if all sprites have an even number of pixels per row.

NoCforMe

Quote from: popcalent on February 18, 2024, 12:57:52 PMI still need to figure out this part:

MOV    AL, BYTE PTR [DI]      ;GET PIXEL COLOR
MOV    ES:[SI], AL            ;PUT PIXEL COLOR

and see if I can do MOVSW because I'm not sure if all sprites have an even number of pixels per row.

You could switch SI and DI and use MOVSB, which will also increment both SI and DI. Should be a little bit faster.

You could use MOVSW to move most of your data, then if there's a leftover byte at the end use MOVSB. Would require a little more calculation code, but not much.

Something like this:
; Move words:
; Set up SI & DI.
MOV CX, <sprite data len>
SHR CX, 1 ;/2.
REP MOVSW

TEST <sprite data len>, 1 ;Even or odd?
JZ isEven ;  Even.

; Move extra byte @ end:
MOVSB

isEven: . . .
Assembly language programming should be fun. That's why I do it.

popcalent

I haven't had a chance to post because I've been busy, but I'm still working on my keyboard interrupt program. At the moment, I made it so when I press a key, the handler checks if it's interrupt 4Fh, and if it is, it puts AL in PRESSED_KEY. It also prints AL plus 48, but this is for debugging and it will eventually go.

Then the main loop evaluates PRESSED_KEY, and if it's ESC, it exits the program, and, if it isn't, it loops. The problem is that sometimes I press ESC, the handler does but it's supposed to do, but the loop doesn't. I thought that this is because I press ESC after the loop evaluates PRESSED_KEY, then I release it before the loop evaluates PRESSED_KEY again. So the ESC scan code is never evaluated. However, I've tried pressing ESC and not releasing, and it takes a couple loops for the program to exit.

I thought that I could make it so if ESC is released, the handler does nothing, so the loop can evaluate ESC in the next loop. However, this doesn't solve anything... There are instances where I have to press ESC three or four times for the program to exit, and then the DOS prompt has three or four "\", each one resulting from each pressed ESC...

This is the code:

;#################################################
;#
;# PRESS_ESC.ASM
;#
;#################################################

;#################################################
;# DEFINITIONS
;#################################################
        KB_ESC          EQU    01h
        REL_ESC          EQU    81h
;#################################################
;# STACK
;#################################################
STACKSG SEGMENT STACK
        db 4096 dup (?)  ;make a 4KB stack
        STACKSG ENDS
;#################################################
;# DATA
;#################################################
DATASG SEGMENT PARA 'Data'
        DATASG ENDS

;#################################################
;# PROGRAM STARTS HERE
;#################################################
CODESG SEGMENT PARA 'Code'
PRESS_ESC PROC FAR
        screen_address  dw 0
        OLD_INT15_VECTOR        LABEL DWORD
        OLD_INT15_OFF          DW ?
        OLD_INT15_SEG          DW ?
        PRESSED_KEY    DB 0
        ASSUME CS:CODESG, DS:DATASG
        MOV AX, DATASG
        MOV DS, AX
        CALL    MODE13H_SET
        CALL    SET_INT15H
        MAIN_LOOP:

                MOV    AL, PRESSED_KEY

                CMP    AL, KB_ESC
                JE      CALL_EXIT_TO_MSDOS

                MOV    PRESSED_KEY, 0          ;For future use
                JMP    MAIN_LOOP

                CALL_EXIT_TO_MSDOS:
                        CALL    EXIT_TO_MSDOS

                JMP    MAIN_LOOP              ;For future use
        PRESS_ESC ENDP
;#################################################
;# SUBROUTINE: EXIT_TO_MSDOS
;#################################################
EXIT_TO_MSDOS PROC NEAR
        FLUSH_KEYBOARD:
                MOV    AH, 0  ;READ FROM KB BUFFER
                INT    16H
                CMP    AH, KB_ESC
                JNE    FLUSH_KEYBOARD
        CALL    MODE03H_SET
        CALL    UNSET_INT15H
        MOV AH, 4CH            ; GO TO MSDOS
        INT 21H
        RET
        EXIT_TO_MSDOS ENDP

;#################################################
;# SUBROUTINE: UNSET_INT15H
;#################################################
UNSET_INT15H PROC NEAR
        PUSH    DX
        PUSH    ES                      ;Save segment reg.
        XOR    AX, AX                  ;Point to "zero segment".
        MOV    ES, AX
        MOV    BX, 15h * 4            ;Address of INT 15h vector.

        CLI                            ;Disable interrupts.
        MOV    AX, OLD_INT15_OFF      ; Restore original offset.
        MOV    ES:[BX], AX
        MOV    AX, OLD_INT15_SEG      ; Restore original segment.
        MOV    ES:[BX + 2], AX
        STI                            ;Re-enable interrupts.

        POP    ES                      ;Restore segment reg.
        POP    DX
        RET
        UNSET_INT15H ENDP

;#################################################
;# SUBROUTINE: SET_INT15H
;#################################################
SET_INT15H PROC NEAR
        PUSH    DX
        PUSH    ES                      ;Save segment reg.
        XOR    AX, AX                  ;Point to "zero segment".
        MOV    ES, AX
        MOV    BX, 15h * 4            ;Address of INT 15h vector.
        MOV    AX, ES:[BX]            ;Get offset.
        MOV    OLD_INT15_OFF, AX
        MOV    AX, ES:[BX + 2]        ;Get segment.
        MOV    OLD_INT15_SEG, AX

        CLI                            ;Disable interrupts.
        MOV    AX, OFFSET INT_15H_HANDLER
        MOV    ES:[BX], AX            ;Plug in new offset.
        MOV    AX, CS                  ;Get our code segment.
        MOV    ES:[BX + 2], AX        ;Plug it in.
        STI                            ;Re-enable interrupts.
        POP    ES                      ;Restore segment reg.
        POP    DX
        RET
        SET_INT15H ENDP

;#################################################
;# INTERRUPT HANDLER
;#################################################
INT_15H_HANDLER PROC NEAR
        CMP    AH, 4Fh
        JNE    EXIT_HANDLER

        CMP    AL, REL_ESC
        JE      EXIT_HANDLER
                ;PRINT SCAN CODE
                MOV    AH, 09H
                MOV    BL, 7          ;COLOR
                MOV    CX, 1          ;NUMBER OF CHARS
                ADD    AL, 48
                INT    10H
                SUB    AL, 48

        PUSH    ES
        MOV    DX, DATASG
        MOV    ES, DX
        MOV    ES:PRESSED_KEY, AL
        POP    ES

        EXIT_HANDLER:
                JMP    CS:[OLD_INT15_VECTOR]
        RET
        INT_15H_HANDLER ENDP

;#################################################
;# SUBROUTINE: MODE13H_SET
;#################################################
MODE13H_SET PROC NEAR
        PUSH    AX
        MOV    AX, 13H
        INT    10H
        POP    AX
        RET
        MODE13H_SET ENDP

;#################################################
;# SUBROUTINE: MODE03H_SET
;#################################################
MODE03H_SET PROC NEAR
        PUSH    AX
        MOV    AX, 03H
        INT    10H
        POP    AX
        RET
        MODE03H_SET ENDP


CODESG ENDS
END PRESS_ESC

NoCforMe

Problems:
  • You still seem confused about segment usage here. All your data is in your code segment (including PRESSED_KEY), but you seem to be accessing it wrong: you have ASSUME CS:CODESG, DS:DATASG, which I think means that any data accesses (like PRESSED_KEY) will be done using DS, which is wrong, since you have it pointing to that (empty) data segment.
    Just to be sure, I would use explicit segment overrides for that data that's in the code segment:
    MOV AL, CS:PRESSED_KEY

    MOV CS: PRESSED_KEY, 0 For future use
  • Your code segment has data at its entry point. That "data" will be executed as code, with who knows what effect. You should either move the data to the end of that segment, after your return to DOS, or put a jump around it:
    PRESS_ESC: JMP realEP ;Program entry point

    ; Data in code segment:
    screen_address dw 0
    OLD_INT15_VECTOR LABEL DWORD
      OLD_INT15_OFF DW ?
      OLD_INT15_SEG DW ?
    PRESSED_KEY DB 0

    realEP: ;Actual code entry point
  • You have no data in your DATA segment, so just get rid of it. (No requirement that a program have a data segment.)

I'm also not sure why you have your entry-point code set up as a PROC, since you will never return from it. Once you exit to DOS w/INT 21/AH=4Ch, that's the end of the show; no more of your code will execute after that, so there's no need of any RETs.

None of which might explain why you're having problems, but still things that should be fixed.
Assembly language programming should be fun. That's why I do it.

popcalent

Thanks for your help.

Quote from: NoCforMe on February 28, 2024, 08:06:35 AMJust to be sure, I would use explicit segment overrides for that data that's in the code segment:
Corrected

Quote from: NoCforMe on February 28, 2024, 08:06:35 AMYour code segment has data at its entry point. That "data" will be executed as code, with who knows what effect. You should either move the data to the end of that segment, after your return to DOS,
  • You have no data in your DATA segment, so just get rid of it. (No requirement that a program have a data segment.)
Ok. I put all the data after the JMP MAIN_LOOP, which is a portion of code that is unreachable.

Quote from: NoCforMe on February 28, 2024, 08:06:35 AMI'm also not sure why you have your entry-point code set up as a PROC, since you will never return from it. Once you exit to DOS w/INT 21/AH=4Ch, that's the end of the show; no more of your code will execute after that, so there's no need of any RETs.
I know. It's just that I end every procedure with a RET out of good practice.

Quote from: NoCforMe on February 28, 2024, 08:06:35 AMNone of which might explain why you're having problems
Unfortunately, you're right.

NoCforMe

Hmm; OK, I went through your code again. If you didn't change the code at the end of your INT 15h handler, then that needs changed:
    PUSH   ES
    MOV    DX, DATASG
    MOV    ES, DX
    MOV    ES:PRESSED_KEY, AL
    POP    ES
should be just:
    MOV    CS:PRESSED_KEY, AL
Again, confusion over segments. Since that variable is in the code segment, just use the CS: segment override to access it.

Also, your interrupt handler should be as lean and mean as possible. Don't call any OS services from it, like INT 10. If you want to display a character, do it in your code outside of the ISR. You want to spend as little time as possible inside the ISR.

Other than that, I can't see anything wrong ...
Assembly language programming should be fun. That's why I do it.

popcalent

Quote from: NoCforMe on February 28, 2024, 12:25:32 PMAgain, confusion over segments. Since that variable is in the code segment, just use the CS: segment override to access it.
Yes, that's a remnant from when PRESSED_KEY was in DS.

Quote from: NoCforMe on February 28, 2024, 12:25:32 PMAlso, your interrupt handler should be as lean and mean as possible. Don't call any OS services from it, like INT 10. If you want to display a character, do it in your code outside of the ISR. You want to spend as little time as possible inside the ISR.
Yeah, that's there just for debugging, but it has to go. I actually just commented it.

Quote from: NoCforMe on February 28, 2024, 12:25:32 PMOther than that, I can't see anything wrong ...
Unfortunately, it doesn't work. Sometimes, I press ESC and the program exits immediately. Sometimes, I have to press ESC multiple times, then there are as many slashes on the DOS prompt as times I pressed ESC... How is this so complicated? All professional games and many homebrews have immediate response to cursor keys.What are they doing that is so obscure that I can't do?

sinsi

Are you running under an emulator?
Are you building under an emulator?
TASM?

I've tested it with FASM and it works properly, even moves a sprite around (at 18 pixels per second :biggrin: )
🍺🍺🍺

NoCforMe

I hear your frustration.

Scan code: you're apparently checking for a release code (81h) which may or may not come through INT 15h. Have you tried using the value 1, which is the scan code for ESC?
Assembly language programming should be fun. That's why I do it.

popcalent

Quote from: sinsi on February 28, 2024, 01:58:50 PMAre you running under an emulator?
Are you building under an emulator?
TASM?
Yes to both.

Quote from: sinsi on February 28, 2024, 01:58:50 PMI've tested it with FASM and it works properly,
The code I posted?

popcalent

Quote from: NoCforMe on February 28, 2024, 02:00:41 PMI hear your frustration.

Scan code: you're apparently checking for a release code (81h) which may or may not come through INT 15h. Have you tried using the value 1, which is the scan code for ESC?

The reason I check for release of ESC (81h) is because I thought my code wasn't working due to the fact that sometimes I would press ESC when the IP was after the "CMP AL, KB_ESC" in the MAIN_LOOP, and then I would release ESC when the IP was before the "CMP AL, KB_ESC" without giving the code in the MAIN_LOOP a chance to evaluate the scan code for ESC. So I thought I would not store the release code for ESC to make sure that, once I press ESC, it gets evaluated in the MAIN_LOOP (as opposed to being overwritten by the release code of ESC before being evaluated). The program doesn't work either way and it shows the same behavior.

And yes, I assume that the loop is so fast that there's no chance I press and release ESC within the same iteration of the loop, but I didn't know what else to try.

NoCforMe

OK, some (possibly) bad news, but maybe this'll help eventually:

Looks like using INT 15/AH = 4Fh might not be the way to go. I just looked at a page which says:
QuoteAH = 4F
AL = scan code
CF = set to 1 (via STC instruction)

on return

AH = 80h, CF set  (PC, PCjr)
  = 86h, CF set  (XT BIOS 11/8/82, AT BIOS 1/10/84)
AL = CF set, new scan code
  = CF clear, original scancode

- available with XT BIOS after 11/8/82, AT BIOS after 1/10/84
- called by INT 9, makes allowance for keyboard translation
- normally returns the scan code in AL, with CF set
- if function returns with CF clear, INT 9 ignores keystroke
- do not rely on this function being called for each INT 9 since
  any user INT 9 handler can exit prematurely and circumvent
  this function

So maybe you do need to hook the actual keyboard BIOS interrupt (INT 9) and store keycodes there. I know that's do-able.

Also, question for @sinsi: Are we doing wrong here by JMPing to the old INT 15h vector? Should we instead be just doing an IRET inside the ISR? (Assuming we continue using INT 15h.)
Assembly language programming should be fun. That's why I do it.

sinsi

Which emulator? Some of them are known for timing issues which can affect keyboard input.
The FASM code I have uses a 128-byte key array, not just a single byte, but when I converted your code it exited as soon as ESC was pressed.
🍺🍺🍺

_japheth


Hello,

it's an interesting "race condition" problem. You're writing to variable PRESSED_KEY in both your main loop and your interrupt procedure. That cannot work without some "synchronization". The best and simplest approach of course is NOT to write to that variable in your main loop.

Dummheit, gepaart mit Dreistigkeit - eine furchtbare Macht.