1/**************************************************************************** 2* 3* VBE 2.0 Linear Framebuffer Profiler 4* By Kendall Bennett and Brian Hook 5* 6* Filename: LFBPROF.C 7* Language: ANSI C 8* Environment: Watcom C/C++ 10.0a with DOS4GW 9* 10* Description: Simple program to profile the speed of screen clearing 11* and full screen BitBlt operations using a VESA VBE 2.0 12* linear framebuffer from 32 bit protected mode. 13* 14* For simplicity, this program only supports 256 color 15* SuperVGA video modes that support a linear framebuffer. 16* 17* 18* 2002/02/18: Jeroen Janssen <japj at xs4all dot nl> 19* - fixed unsigned short for mode list (-1 != 0xffff otherwise) 20* - fixed LfbMapRealPointer macro mask problem (some modes were skipped) 21* 22****************************************************************************/ 23 24#include <stdio.h> 25#include <stdlib.h> 26#include <string.h> 27#include <conio.h> 28#include <dos.h> 29#include "lfbprof.h" 30 31/*---------------------------- Global Variables ---------------------------*/ 32 33int VESABuf_len = 1024; /* Length of VESABuf */ 34int VESABuf_sel = 0; /* Selector for VESABuf */ 35int VESABuf_rseg; /* Real mode segment of VESABuf */ 36unsigned short modeList[50]; /* List of available VBE modes */ 37float clearsPerSec; /* Number of clears per second */ 38float clearsMbPerSec; /* Memory transfer for clears */ 39float bitBltsPerSec; /* Number of BitBlt's per second */ 40float bitBltsMbPerSec; /* Memory transfer for bitblt's */ 41int xres,yres; /* Video mode resolution */ 42int bytesperline; /* Bytes per scanline for mode */ 43long imageSize; /* Length of the video image */ 44char *LFBPtr; /* Pointer to linear framebuffer */ 45 46/*------------------------- DPMI interface routines -----------------------*/ 47 48void DPMI_allocRealSeg(int size,int *sel,int *r_seg) 49/**************************************************************************** 50* 51* Function: DPMI_allocRealSeg 52* Parameters: size - Size of memory block to allocate 53* sel - Place to return protected mode selector 54* r_seg - Place to return real mode segment 55* 56* Description: Allocates a block of real mode memory using DPMI services. 57* This routine returns both a protected mode selector and 58* real mode segment for accessing the memory block. 59* 60****************************************************************************/ 61{ 62 union REGS r; 63 64 r.w.ax = 0x100; /* DPMI allocate DOS memory */ 65 r.w.bx = (size + 0xF) >> 4; /* number of paragraphs */ 66 int386(0x31, &r, &r); 67 if (r.w.cflag) 68 FatalError("DPMI_allocRealSeg failed!"); 69 *sel = r.w.dx; /* Protected mode selector */ 70 *r_seg = r.w.ax; /* Real mode segment */ 71} 72 73void DPMI_freeRealSeg(unsigned sel) 74/**************************************************************************** 75* 76* Function: DPMI_allocRealSeg 77* Parameters: sel - Protected mode selector of block to free 78* 79* Description: Frees a block of real mode memory. 80* 81****************************************************************************/ 82{ 83 union REGS r; 84 85 r.w.ax = 0x101; /* DPMI free DOS memory */ 86 r.w.dx = sel; /* DX := selector from 0x100 */ 87 int386(0x31, &r, &r); 88} 89 90typedef struct { 91 long edi; 92 long esi; 93 long ebp; 94 long reserved; 95 long ebx; 96 long edx; 97 long ecx; 98 long eax; 99 short flags; 100 short es,ds,fs,gs,ip,cs,sp,ss; 101 } _RMREGS; 102 103#define IN(reg) rmregs.e##reg = in->x.reg 104#define OUT(reg) out->x.reg = rmregs.e##reg 105 106int DPMI_int86(int intno, RMREGS *in, RMREGS *out) 107/**************************************************************************** 108* 109* Function: DPMI_int86 110* Parameters: intno - Interrupt number to issue 111* in - Pointer to structure for input registers 112* out - Pointer to structure for output registers 113* Returns: Value returned by interrupt in AX 114* 115* Description: Issues a real mode interrupt using DPMI services. 116* 117****************************************************************************/ 118{ 119 _RMREGS rmregs; 120 union REGS r; 121 struct SREGS sr; 122 123 memset(&rmregs, 0, sizeof(rmregs)); 124 IN(ax); IN(bx); IN(cx); IN(dx); IN(si); IN(di); 125 126 segread(&sr); 127 r.w.ax = 0x300; /* DPMI issue real interrupt */ 128 r.h.bl = intno; 129 r.h.bh = 0; 130 r.w.cx = 0; 131 sr.es = sr.ds; 132 r.x.edi = (unsigned)&rmregs; 133 int386x(0x31, &r, &r, &sr); /* Issue the interrupt */ 134 135 OUT(ax); OUT(bx); OUT(cx); OUT(dx); OUT(si); OUT(di); 136 out->x.cflag = rmregs.flags & 0x1; 137 return out->x.ax; 138} 139 140int DPMI_int86x(int intno, RMREGS *in, RMREGS *out, RMSREGS *sregs) 141/**************************************************************************** 142* 143* Function: DPMI_int86 144* Parameters: intno - Interrupt number to issue 145* in - Pointer to structure for input registers 146* out - Pointer to structure for output registers 147* sregs - Values to load into segment registers 148* Returns: Value returned by interrupt in AX 149* 150* Description: Issues a real mode interrupt using DPMI services. 151* 152****************************************************************************/ 153{ 154 _RMREGS rmregs; 155 union REGS r; 156 struct SREGS sr; 157 158 memset(&rmregs, 0, sizeof(rmregs)); 159 IN(ax); IN(bx); IN(cx); IN(dx); IN(si); IN(di); 160 rmregs.es = sregs->es; 161 rmregs.ds = sregs->ds; 162 163 segread(&sr); 164 r.w.ax = 0x300; /* DPMI issue real interrupt */ 165 r.h.bl = intno; 166 r.h.bh = 0; 167 r.w.cx = 0; 168 sr.es = sr.ds; 169 r.x.edi = (unsigned)&rmregs; 170 int386x(0x31, &r, &r, &sr); /* Issue the interrupt */ 171 172 OUT(ax); OUT(bx); OUT(cx); OUT(dx); OUT(si); OUT(di); 173 sregs->es = rmregs.es; 174 sregs->cs = rmregs.cs; 175 sregs->ss = rmregs.ss; 176 sregs->ds = rmregs.ds; 177 out->x.cflag = rmregs.flags & 0x1; 178 return out->x.ax; 179} 180 181int DPMI_allocSelector(void) 182/**************************************************************************** 183* 184* Function: DPMI_allocSelector 185* Returns: Newly allocated protected mode selector 186* 187* Description: Allocates a new protected mode selector using DPMI 188* services. This selector has a base address and limit of 0. 189* 190****************************************************************************/ 191{ 192 int sel; 193 union REGS r; 194 195 r.w.ax = 0; /* DPMI allocate selector */ 196 r.w.cx = 1; /* Allocate a single selector */ 197 int386(0x31, &r, &r); 198 if (r.x.cflag) 199 FatalError("DPMI_allocSelector() failed!"); 200 sel = r.w.ax; 201 202 r.w.ax = 9; /* DPMI set access rights */ 203 r.w.bx = sel; 204 r.w.cx = 0x8092; /* 32 bit page granular */ 205 int386(0x31, &r, &r); 206 return sel; 207} 208 209long DPMI_mapPhysicalToLinear(long physAddr,long limit) 210/**************************************************************************** 211* 212* Function: DPMI_mapPhysicalToLinear 213* Parameters: physAddr - Physical memory address to map 214* limit - Length-1 of physical memory region to map 215* Returns: Starting linear address for mapped memory 216* 217* Description: Maps a section of physical memory into the linear address 218* space of a process using DPMI calls. Note that this linear 219* address cannot be used directly, but must be used as the 220* base address for a selector. 221* 222****************************************************************************/ 223{ 224 union REGS r; 225 226 r.w.ax = 0x800; /* DPMI map physical to linear */ 227 r.w.bx = physAddr >> 16; 228 r.w.cx = physAddr & 0xFFFF; 229 r.w.si = limit >> 16; 230 r.w.di = limit & 0xFFFF; 231 int386(0x31, &r, &r); 232 if (r.x.cflag) 233 FatalError("DPMI_mapPhysicalToLinear() failed!"); 234 return ((long)r.w.bx << 16) + r.w.cx; 235} 236 237void DPMI_setSelectorBase(int sel,long linAddr) 238/**************************************************************************** 239* 240* Function: DPMI_setSelectorBase 241* Parameters: sel - Selector to change base address for 242* linAddr - Linear address used for new base address 243* 244* Description: Sets the base address for the specified selector. 245* 246****************************************************************************/ 247{ 248 union REGS r; 249 250 r.w.ax = 7; /* DPMI set selector base address */ 251 r.w.bx = sel; 252 r.w.cx = linAddr >> 16; 253 r.w.dx = linAddr & 0xFFFF; 254 int386(0x31, &r, &r); 255 if (r.x.cflag) 256 FatalError("DPMI_setSelectorBase() failed!"); 257} 258 259void DPMI_setSelectorLimit(int sel,long limit) 260/**************************************************************************** 261* 262* Function: DPMI_setSelectorLimit 263* Parameters: sel - Selector to change limit for 264* limit - Limit-1 for the selector 265* 266* Description: Sets the memory limit for the specified selector. 267* 268****************************************************************************/ 269{ 270 union REGS r; 271 272 r.w.ax = 8; /* DPMI set selector limit */ 273 r.w.bx = sel; 274 r.w.cx = limit >> 16; 275 r.w.dx = limit & 0xFFFF; 276 int386(0x31, &r, &r); 277 if (r.x.cflag) 278 FatalError("DPMI_setSelectorLimit() failed!"); 279} 280 281/*-------------------------- VBE Interface routines -----------------------*/ 282 283void FatalError(char *msg) 284{ 285 fprintf(stderr,"%s\n", msg); 286 exit(1); 287} 288 289static void ExitVBEBuf(void) 290{ 291 DPMI_freeRealSeg(VESABuf_sel); 292} 293 294void VBE_initRMBuf(void) 295/**************************************************************************** 296* 297* Function: VBE_initRMBuf 298* Description: Initialises the VBE transfer buffer in real mode memory. 299* This routine is called by the VESAVBE module every time 300* it needs to use the transfer buffer, so we simply allocate 301* it once and then return. 302* 303****************************************************************************/ 304{ 305 if (!VESABuf_sel) { 306 DPMI_allocRealSeg(VESABuf_len, &VESABuf_sel, &VESABuf_rseg); 307 atexit(ExitVBEBuf); 308 } 309} 310 311void VBE_callESDI(RMREGS *regs, void *buffer, int size) 312/**************************************************************************** 313* 314* Function: VBE_callESDI 315* Parameters: regs - Registers to load when calling VBE 316* buffer - Buffer to copy VBE info block to 317* size - Size of buffer to fill 318* 319* Description: Calls the VESA VBE and passes in a buffer for the VBE to 320* store information in, which is then copied into the users 321* buffer space. This works in protected mode as the buffer 322* passed to the VESA VBE is allocated in conventional 323* memory, and is then copied into the users memory block. 324* 325****************************************************************************/ 326{ 327 RMSREGS sregs; 328 329 VBE_initRMBuf(); 330 sregs.es = VESABuf_rseg; 331 regs->x.di = 0; 332 _fmemcpy(MK_FP(VESABuf_sel,0),buffer,size); 333 DPMI_int86x(0x10, regs, regs, &sregs); 334 _fmemcpy(buffer,MK_FP(VESABuf_sel,0),size); 335} 336 337int VBE_detect(void) 338/**************************************************************************** 339* 340* Function: VBE_detect 341* Parameters: vgaInfo - Place to store the VGA information block 342* Returns: VBE version number, or 0 if not detected. 343* 344* Description: Detects if a VESA VBE is out there and functioning 345* correctly. If we detect a VBE interface we return the 346* VGAInfoBlock returned by the VBE and the VBE version number. 347* 348****************************************************************************/ 349{ 350 RMREGS regs; 351 unsigned short *p1,*p2; 352 VBE_vgaInfo vgaInfo; 353 354 /* Put 'VBE2' into the signature area so that the VBE 2.0 BIOS knows 355 * that we have passed a 512 byte extended block to it, and wish 356 * the extended information to be filled in. 357 */ 358 strncpy(vgaInfo.VESASignature,"VBE2",4); 359 360 /* Get the SuperVGA Information block */ 361 regs.x.ax = 0x4F00; 362 VBE_callESDI(®s, &vgaInfo, sizeof(VBE_vgaInfo)); 363 if (regs.x.ax != 0x004F) 364 return 0; 365 if (strncmp(vgaInfo.VESASignature,"VESA",4) != 0) 366 return 0; 367 368 /* Now that we have detected a VBE interface, copy the list of available 369 * video modes into our local buffer. We *must* copy this mode list, 370 * since the VBE will build the mode list in the VBE_vgaInfo buffer 371 * that we have passed, so the next call to the VBE will trash the 372 * list of modes. 373 */ 374 printf("videomodeptr %x\n",vgaInfo.VideoModePtr); 375 p1 = LfbMapRealPointer(vgaInfo.VideoModePtr); 376 p2 = modeList; 377 while (*p1 != -1) 378 { 379 printf("found mode %x\n",*p1); 380 *p2++ = *p1++; 381 } 382 *p2 = -1; 383 return vgaInfo.VESAVersion; 384} 385 386int VBE_getModeInfo(int mode,VBE_modeInfo *modeInfo) 387/**************************************************************************** 388* 389* Function: VBE_getModeInfo 390* Parameters: mode - VBE mode to get information for 391* modeInfo - Place to store VBE mode information 392* Returns: 1 on success, 0 if function failed. 393* 394* Description: Obtains information about a specific video mode from the 395* VBE. You should use this function to find the video mode 396* you wish to set, as the new VBE 2.0 mode numbers may be 397* completely arbitrary. 398* 399****************************************************************************/ 400{ 401 RMREGS regs; 402 403 regs.x.ax = 0x4F01; /* Get mode information */ 404 regs.x.cx = mode; 405 VBE_callESDI(®s, modeInfo, sizeof(VBE_modeInfo)); 406 if (regs.x.ax != 0x004F) 407 return 0; 408 if ((modeInfo->ModeAttributes & vbeMdAvailable) == 0) 409 return 0; 410 return 1; 411} 412 413void VBE_setVideoMode(int mode) 414/**************************************************************************** 415* 416* Function: VBE_setVideoMode 417* Parameters: mode - VBE mode number to initialise 418* 419****************************************************************************/ 420{ 421 RMREGS regs; 422 regs.x.ax = 0x4F02; 423 regs.x.bx = mode; 424 DPMI_int86(0x10,®s,®s); 425} 426 427/*-------------------- Application specific routines ----------------------*/ 428 429void *GetPtrToLFB(long physAddr) 430/**************************************************************************** 431* 432* Function: GetPtrToLFB 433* Parameters: physAddr - Physical memory address of linear framebuffer 434* Returns: Far pointer to the linear framebuffer memory 435* 436****************************************************************************/ 437{ 438 int sel; 439 long linAddr,limit = (4096 * 1024) - 1; 440 441// sel = DPMI_allocSelector(); 442 linAddr = DPMI_mapPhysicalToLinear(physAddr,limit); 443// DPMI_setSelectorBase(sel,linAddr); 444// DPMI_setSelectorLimit(sel,limit); 445// return MK_FP(sel,0); 446 return (void*)linAddr; 447} 448 449void AvailableModes(void) 450/**************************************************************************** 451* 452* Function: AvailableModes 453* 454* Description: Display a list of available LFB mode resolutions. 455* 456****************************************************************************/ 457{ 458 unsigned short *p; 459 VBE_modeInfo modeInfo; 460 461 printf("Usage: LFBPROF <xres> <yres>\n\n"); 462 printf("Available 256 color video modes:\n"); 463 for (p = modeList; *p != -1; p++) { 464 if (VBE_getModeInfo(*p, &modeInfo)) { 465 /* Filter out only 8 bit linear framebuffer modes */ 466 if ((modeInfo.ModeAttributes & vbeMdLinear) == 0) 467 continue; 468 if (modeInfo.MemoryModel != vbeMemPK 469 || modeInfo.BitsPerPixel != 8 470 || modeInfo.NumberOfPlanes != 1) 471 continue; 472 printf(" %4d x %4d %d bits per pixel\n", 473 modeInfo.XResolution, modeInfo.YResolution, 474 modeInfo.BitsPerPixel); 475 } 476 } 477 exit(1); 478} 479 480void InitGraphics(int x,int y) 481/**************************************************************************** 482* 483* Function: InitGraphics 484* Parameters: x,y - Requested video mode resolution 485* 486* Description: Initialise the specified video mode. We search through 487* the list of available video modes for one that matches 488* the resolution and color depth are are looking for. 489* 490****************************************************************************/ 491{ 492 unsigned short *p; 493 VBE_modeInfo modeInfo; 494 printf("InitGraphics\n"); 495 496 for (p = modeList; *p != -1; p++) { 497 if (VBE_getModeInfo(*p, &modeInfo)) { 498 /* Filter out only 8 bit linear framebuffer modes */ 499 if ((modeInfo.ModeAttributes & vbeMdLinear) == 0) 500 continue; 501 if (modeInfo.MemoryModel != vbeMemPK 502 || modeInfo.BitsPerPixel != 8 503 || modeInfo.NumberOfPlanes != 1) 504 continue; 505 if (modeInfo.XResolution != x || modeInfo.YResolution != y) 506 continue; 507 xres = x; 508 yres = y; 509 bytesperline = modeInfo.BytesPerScanLine; 510 imageSize = bytesperline * yres; 511 VBE_setVideoMode(*p | vbeUseLFB); 512 LFBPtr = GetPtrToLFB(modeInfo.PhysBasePtr); 513 return; 514 } 515 } 516 printf("Valid video mode not found\n"); 517 exit(1); 518} 519 520void EndGraphics(void) 521/**************************************************************************** 522* 523* Function: EndGraphics 524* 525* Description: Restores text mode. 526* 527****************************************************************************/ 528{ 529 RMREGS regs; 530 printf("EndGraphics\n"); 531 regs.x.ax = 0x3; 532 DPMI_int86(0x10, ®s, ®s); 533} 534 535void ProfileMode(void) 536/**************************************************************************** 537* 538* Function: ProfileMode 539* 540* Description: Profiles framebuffer performance for simple screen clearing 541* and for copying from system memory to video memory (BitBlt). 542* This routine thrashes the CPU cache by cycling through 543* enough system memory buffers to invalidate the entire 544* CPU external cache before re-using the first memory buffer 545* again. 546* 547****************************************************************************/ 548{ 549 int i,numClears,numBlts,maxImages; 550 long startTicks,endTicks; 551 void *image[10],*dst; 552 printf("ProfileMode\n"); 553 554 /* Profile screen clearing operation */ 555 startTicks = LfbGetTicks(); 556 numClears = 0; 557 while ((LfbGetTicks() - startTicks) < 182) 558 LfbMemset(LFBPtr,numClears++,imageSize); 559 endTicks = LfbGetTicks(); 560 clearsPerSec = numClears / ((endTicks - startTicks) * 0.054925); 561 clearsMbPerSec = (clearsPerSec * imageSize) / 1048576.0; 562 563 /* Profile system memory to video memory copies */ 564 maxImages = ((512 * 1024U) / imageSize) + 2; 565 for (i = 0; i < maxImages; i++) { 566 image[i] = malloc(imageSize); 567 if (image[i] == NULL) 568 FatalError("Not enough memory to profile BitBlt!"); 569 memset(image[i],i+1,imageSize); 570 } 571 startTicks = LfbGetTicks(); 572 numBlts = 0; 573 while ((LfbGetTicks() - startTicks) < 182) 574 LfbMemcpy(LFBPtr,image[numBlts++ % maxImages],imageSize); 575 endTicks = LfbGetTicks(); 576 bitBltsPerSec = numBlts / ((endTicks - startTicks) * 0.054925); 577 bitBltsMbPerSec = (bitBltsPerSec * imageSize) / 1048576.0; 578} 579 580void main(int argc, char *argv[]) 581{ 582 if (VBE_detect() < 0x200) 583 FatalError("This program requires VBE 2.0; Please install UniVBE 5.1."); 584 if (argc != 3) 585 AvailableModes(); /* Display available modes */ 586 587 InitGraphics(atoi(argv[1]),atoi(argv[2])); /* Start graphics */ 588 ProfileMode(); /* Profile the video mode */ 589 EndGraphics(); /* Restore text mode */ 590 591 printf("Profiling results for %dx%d 8 bits per pixel.\n",xres,yres); 592 printf("%3.2f clears/s, %2.2f Mb/s\n", clearsPerSec, clearsMbPerSec); 593 printf("%3.2f bitBlt/s, %2.2f Mb/s\n", bitBltsPerSec, bitBltsMbPerSec); 594} 595