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 33 int VESABuf_len = 1024; /* Length of VESABuf */ 34 int VESABuf_sel = 0; /* Selector for VESABuf */ 35 int VESABuf_rseg; /* Real mode segment of VESABuf */ 36 unsigned short modeList[50]; /* List of available VBE modes */ 37 float clearsPerSec; /* Number of clears per second */ 38 float clearsMbPerSec; /* Memory transfer for clears */ 39 float bitBltsPerSec; /* Number of BitBlt's per second */ 40 float bitBltsMbPerSec; /* Memory transfer for bitblt's */ 41 int xres,yres; /* Video mode resolution */ 42 int bytesperline; /* Bytes per scanline for mode */ 43 long imageSize; /* Length of the video image */ 44 char *LFBPtr; /* Pointer to linear framebuffer */ 45 46 /*------------------------- DPMI interface routines -----------------------*/ 47 48 void 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 73 void 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 90 typedef 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 106 int 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 140 int 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 181 int 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 209 long 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 237 void 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 259 void 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 283 void FatalError(char *msg) 284 { 285 fprintf(stderr,"%s\n", msg); 286 exit(1); 287 } 288 289 static void ExitVBEBuf(void) 290 { 291 DPMI_freeRealSeg(VESABuf_sel); 292 } 293 294 void 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 311 void 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 337 int 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 386 int 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 413 void 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 429 void *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 449 void 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 480 void 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 520 void 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 535 void 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 580 void 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