Reply 20 of 72, by WhiteFalcon
- Rank
- Member
Falcosoft wrote on 2022-06-20, 16:17:Hi, Actually selecting banks/windows is more simple than you think. You should not calculate addresses all the time. There is V […]
WhiteFalcon wrote on 2022-06-20, 11:03:Neat! Thank you for the tips and examples, I will try it on my real PC when I get the chance again. And also here in DOSBox. As you apparently have experiences with this, how do you go about using it for double buffering? I mean you probably set the start offset from 0 to 307200 and back (for 640x480) and it should be possible to use and alternation of the method I mentioned in the first post, storing the background and restoring after the flip. But how about the banks, does the second screen start on a bank boundary, i.e. 307200? Or do you need to just consider it all as one piece of memory which will make the bank calculation for PutImage/Sprite a bit more complicated? I am somewhat afraid the latter is the case, which will again make it a bit over my head. Nevertheless it would make it completely flicker free.
Hi,
Actually selecting banks/windows is more simple than you think. You should not calculate addresses all the time. There is VESA function 05h Display Window Control. Notice that 'Window' is a different concept to multiple display pages. It works only in Banked video modes to overcome the 64K limit of the real mode video segment. This function has to be used in every mode where the mode requires more than 64K to present the full resolution regardless virtual pages are used or not.
Back to the point: VESA function 05h needs to be used only in the lowest level in your Putpixel (or similar routine). Namely:
mov ax, 0a000h
mov es, ax ; es segment register stores video segment all the time.
...
...
PUTPIXEL PROC ; on enter dx contains original Window number (0 at start)
push dx
mov ax, word ptr[Y_coordinate]
mul word ptr [modeinfo.BytesPerScanLine] ; scanline in bytes. If you use Function 06h to set a different virtual width then you should use the value returned in BX instead.
mov di, ax
add di, word ptr[X_coordinate]
adc dx,0
pop cx
cmp dx,cx
je @NOWINDOWCHANGE
mov ax,4f05h ;Window number = dx and Window number is already in dx !
xor bx, bx ;bh=function(0) bl=Window A(0)
int 10h
@NOWINDOWCHANGE:
mov al, byte ptr[Color]
stosb ;Store al at address ES:DI
ret
PUTPIXEL ENDP
The above code should work regardless you set the virtual display width to 2x the screen width for double buffering or you use 2x the screen height (2x scan lines).
BTW there is a description in VBE 3.0. documentation how triple buffering works. The idea is the same for double buffering it is just more simple:Using Hardware Triple Buffering Hardware triple buffering is supported in the VBE/Core specification by allowing the application […]
Using Hardware Triple Buffering
Hardware triple buffering is supported in the VBE/Core specification by allowing the application
to schedule a display start address change, and then providing a function that can be called to
determine the status of the last scheduled display start address change. VBE Function 4F07h is
used for this, along with subfunctions 02h and 04h. To implement hardware triple buffering you
would use the following steps:
1. Display from the first visible buffer, and render to the second hidden buffer.
2. Schedule a display start address change for the second hidden buffer that you just
finished rendering (4F07h, 02h), and start rendering immediately to the third hidden
buffer. CRT controller is currently displaying from the first buffer.
3. Before scheduling the display start address change for the third hidden buffer, wait
until the last scheduled change has occurred (4F07h, 04h returns not 0). Schedule
display start address change for the third hidden buffer and immediately begin
rendering to the first hidden buffer. CRT controller is currently displaying from the
second buffer.
4. Repeat step 3 over and over cycling though each of the buffers.The point is: for 640x480 double buffering you can either define an 1280x480 virtual screen with the help of Function 06h or use a 640x960 virtual screen.
In the first case your page 1 is from x=0 and your page 2 is from x=640.
In the second case your page 1 is from y=0 and your page 2 is from y=480.
In both cases you can use Function 07h to switch between page 1 and page 2.
While your page 1 is visible you draw on page 2 and vice versa.
Yes, I have been using function 05 this way for PutPixel, which is similar to yours:
void VESA_PutPixel640(long int x, long int y, unsigned char col)
{
asm {
mov eax, [y]
mov ebx, [y]
shl eax, 7
shl ebx, 9
add eax, ebx
mov ebx, [x]
add eax, ebx
mov cl, 0
}
find_bank:
asm {
cmp eax, 0xFFFF
jle found_bank
sub eax, 0x10000
inc cl
jmp find_bank
}
found_bank:
asm {
cmp cl, [vesa_cur_bank]
je same_bank
push eax
mov ah, 0x4F
mov al, 0x05
mov bh, 0x00
mov bl, 0x00
xor dx, dx
mov dl, cl
int 0x10
inc [vesa_cur_bank]
pop eax
}
same_bank:
asm {
mov di, ax
mov ax, 0xA000
mov es, ax
mov cl, [col]
mov es:[di], cl
}
}
I have a PutPixel separately for 640x, 800x and 1024x (not that I would ever need those) just to avoid that costy "mul" and split it into "shl"s, which reqires a constant number to work with.
Later I realized it actually should not be that difficult to use a larger virtual screen, just couldnt wrap my head around the organization of the memory in that case.
PutPixel is actually not something I will use as I need blitting routines for image/transparent sprite instead, to avoid calculating the starting coordinates of every pixel. I have such routines already, calcualate only the top-left corner of the image and then keep moving right by one and adding a skip after every line to continue on a new one, keeping a running offset. Its much faster than using a PutPixel routine.
There are two more things puzzling me:
1) the memory is linear so if the first approach you mention is used, the actual whole line is twice as long as the displayed screen, the other half hidden "to the right" until you use function 07 to switch the display to it. Meaning when you are displaying the first screen, the first line is 0-639 in vram and the second one is NOT 640-1279, but 1280-1919 (still in vram), correct? Hence the gfx card must compose the whole screen of separate 640-byte pieces of its vram. The PutImage routine would have to use modeinfo.BytesPerScanLine to count the next line.
The second approach, lets say 640x960, sounds easier to me as the scanlines are the same as the horizontal resolution (640) and my mind can grasp that easier 😀 BUT, the first screen finishes somewhere in the middle of the fifth bank, can you use function 07 to start the second screen in the middle of a bank, byte-precise? Or do you need to "sacrifice" the rest of the fifth bank and put bank six in the top-left corner? The granularity of bank changing on my S3 Trio is 64K, that much I checked.
2) my head has enough with double buffering, let alone tripple buffering 😁 I have alway used double buffering in the way that I had one buffer that I cleared every frame, drew everything plus sprites on top and copied it over to 0xA000. That was possible in mode VGA 0x13. This is different as you just switch, is it necessary to use that store background-draw-restore background approach? If so, it would mean I have to draw every change in the background twice, to each buffer? Uff. While sprites on top (the cursor) only once? I can imagine syncing this process could be prone to mistakes. Redrawing everything in the other, not displayed buffer would be too slow and out of the question?
Thank you for your time and for the sample program, I will read through it but probably wont be able to compile it my BorlandC is very strong-headed when it comes to inline asm. And I dont kno how to compile a pure asm file.
Olivetti M4 P75, 32MB RAM, 4GB HDD, 8GB CF, CD-ROM, SoundBlaster AWE 64, Gravis Ultrasound MAX, Roland SCC-1, Roland MT-32, Roland CM-64
Intel 486DX2/66Mhz, 16MB RAM, VGA Trident 512kB, 1.6GB HDD WD, CD-ROM, 256MB CF, SoundBlaster 16 Pro (CT2910)