| /**************************************************************************** |
| * |
| * VBE 2.0 Linear Framebuffer Profiler |
| * By Kendall Bennett and Brian Hook |
| * |
| * Filename: LFBPROF.C |
| * Language: ANSI C |
| * Environment: Watcom C/C++ 10.0a with DOS4GW |
| * |
| * Description: Simple program to profile the speed of screen clearing |
| * and full screen BitBlt operations using a VESA VBE 2.0 |
| * linear framebuffer from 32 bit protected mode. |
| * |
| * For simplicity, this program only supports 256 color |
| * SuperVGA video modes that support a linear framebuffer. |
| * |
| * |
| * 2002/02/18: Jeroen Janssen <japj at xs4all dot nl> |
| * - fixed unsigned short for mode list (-1 != 0xffff otherwise) |
| * - fixed LfbMapRealPointer macro mask problem (some modes were skipped) |
| * |
| ****************************************************************************/ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <conio.h> |
| #include <dos.h> |
| #include "lfbprof.h" |
| |
| /*---------------------------- Global Variables ---------------------------*/ |
| |
| int VESABuf_len = 1024; /* Length of VESABuf */ |
| int VESABuf_sel = 0; /* Selector for VESABuf */ |
| int VESABuf_rseg; /* Real mode segment of VESABuf */ |
| unsigned short modeList[50]; /* List of available VBE modes */ |
| float clearsPerSec; /* Number of clears per second */ |
| float clearsMbPerSec; /* Memory transfer for clears */ |
| float bitBltsPerSec; /* Number of BitBlt's per second */ |
| float bitBltsMbPerSec; /* Memory transfer for bitblt's */ |
| int xres,yres; /* Video mode resolution */ |
| int bytesperline; /* Bytes per scanline for mode */ |
| long imageSize; /* Length of the video image */ |
| char *LFBPtr; /* Pointer to linear framebuffer */ |
| |
| /*------------------------- DPMI interface routines -----------------------*/ |
| |
| void DPMI_allocRealSeg(int size,int *sel,int *r_seg) |
| /**************************************************************************** |
| * |
| * Function: DPMI_allocRealSeg |
| * Parameters: size - Size of memory block to allocate |
| * sel - Place to return protected mode selector |
| * r_seg - Place to return real mode segment |
| * |
| * Description: Allocates a block of real mode memory using DPMI services. |
| * This routine returns both a protected mode selector and |
| * real mode segment for accessing the memory block. |
| * |
| ****************************************************************************/ |
| { |
| union REGS r; |
| |
| r.w.ax = 0x100; /* DPMI allocate DOS memory */ |
| r.w.bx = (size + 0xF) >> 4; /* number of paragraphs */ |
| int386(0x31, &r, &r); |
| if (r.w.cflag) |
| FatalError("DPMI_allocRealSeg failed!"); |
| *sel = r.w.dx; /* Protected mode selector */ |
| *r_seg = r.w.ax; /* Real mode segment */ |
| } |
| |
| void DPMI_freeRealSeg(unsigned sel) |
| /**************************************************************************** |
| * |
| * Function: DPMI_allocRealSeg |
| * Parameters: sel - Protected mode selector of block to free |
| * |
| * Description: Frees a block of real mode memory. |
| * |
| ****************************************************************************/ |
| { |
| union REGS r; |
| |
| r.w.ax = 0x101; /* DPMI free DOS memory */ |
| r.w.dx = sel; /* DX := selector from 0x100 */ |
| int386(0x31, &r, &r); |
| } |
| |
| typedef struct { |
| long edi; |
| long esi; |
| long ebp; |
| long reserved; |
| long ebx; |
| long edx; |
| long ecx; |
| long eax; |
| short flags; |
| short es,ds,fs,gs,ip,cs,sp,ss; |
| } _RMREGS; |
| |
| #define IN(reg) rmregs.e##reg = in->x.reg |
| #define OUT(reg) out->x.reg = rmregs.e##reg |
| |
| int DPMI_int86(int intno, RMREGS *in, RMREGS *out) |
| /**************************************************************************** |
| * |
| * Function: DPMI_int86 |
| * Parameters: intno - Interrupt number to issue |
| * in - Pointer to structure for input registers |
| * out - Pointer to structure for output registers |
| * Returns: Value returned by interrupt in AX |
| * |
| * Description: Issues a real mode interrupt using DPMI services. |
| * |
| ****************************************************************************/ |
| { |
| _RMREGS rmregs; |
| union REGS r; |
| struct SREGS sr; |
| |
| memset(&rmregs, 0, sizeof(rmregs)); |
| IN(ax); IN(bx); IN(cx); IN(dx); IN(si); IN(di); |
| |
| segread(&sr); |
| r.w.ax = 0x300; /* DPMI issue real interrupt */ |
| r.h.bl = intno; |
| r.h.bh = 0; |
| r.w.cx = 0; |
| sr.es = sr.ds; |
| r.x.edi = (unsigned)&rmregs; |
| int386x(0x31, &r, &r, &sr); /* Issue the interrupt */ |
| |
| OUT(ax); OUT(bx); OUT(cx); OUT(dx); OUT(si); OUT(di); |
| out->x.cflag = rmregs.flags & 0x1; |
| return out->x.ax; |
| } |
| |
| int DPMI_int86x(int intno, RMREGS *in, RMREGS *out, RMSREGS *sregs) |
| /**************************************************************************** |
| * |
| * Function: DPMI_int86 |
| * Parameters: intno - Interrupt number to issue |
| * in - Pointer to structure for input registers |
| * out - Pointer to structure for output registers |
| * sregs - Values to load into segment registers |
| * Returns: Value returned by interrupt in AX |
| * |
| * Description: Issues a real mode interrupt using DPMI services. |
| * |
| ****************************************************************************/ |
| { |
| _RMREGS rmregs; |
| union REGS r; |
| struct SREGS sr; |
| |
| memset(&rmregs, 0, sizeof(rmregs)); |
| IN(ax); IN(bx); IN(cx); IN(dx); IN(si); IN(di); |
| rmregs.es = sregs->es; |
| rmregs.ds = sregs->ds; |
| |
| segread(&sr); |
| r.w.ax = 0x300; /* DPMI issue real interrupt */ |
| r.h.bl = intno; |
| r.h.bh = 0; |
| r.w.cx = 0; |
| sr.es = sr.ds; |
| r.x.edi = (unsigned)&rmregs; |
| int386x(0x31, &r, &r, &sr); /* Issue the interrupt */ |
| |
| OUT(ax); OUT(bx); OUT(cx); OUT(dx); OUT(si); OUT(di); |
| sregs->es = rmregs.es; |
| sregs->cs = rmregs.cs; |
| sregs->ss = rmregs.ss; |
| sregs->ds = rmregs.ds; |
| out->x.cflag = rmregs.flags & 0x1; |
| return out->x.ax; |
| } |
| |
| int DPMI_allocSelector(void) |
| /**************************************************************************** |
| * |
| * Function: DPMI_allocSelector |
| * Returns: Newly allocated protected mode selector |
| * |
| * Description: Allocates a new protected mode selector using DPMI |
| * services. This selector has a base address and limit of 0. |
| * |
| ****************************************************************************/ |
| { |
| int sel; |
| union REGS r; |
| |
| r.w.ax = 0; /* DPMI allocate selector */ |
| r.w.cx = 1; /* Allocate a single selector */ |
| int386(0x31, &r, &r); |
| if (r.x.cflag) |
| FatalError("DPMI_allocSelector() failed!"); |
| sel = r.w.ax; |
| |
| r.w.ax = 9; /* DPMI set access rights */ |
| r.w.bx = sel; |
| r.w.cx = 0x8092; /* 32 bit page granular */ |
| int386(0x31, &r, &r); |
| return sel; |
| } |
| |
| long DPMI_mapPhysicalToLinear(long physAddr,long limit) |
| /**************************************************************************** |
| * |
| * Function: DPMI_mapPhysicalToLinear |
| * Parameters: physAddr - Physical memory address to map |
| * limit - Length-1 of physical memory region to map |
| * Returns: Starting linear address for mapped memory |
| * |
| * Description: Maps a section of physical memory into the linear address |
| * space of a process using DPMI calls. Note that this linear |
| * address cannot be used directly, but must be used as the |
| * base address for a selector. |
| * |
| ****************************************************************************/ |
| { |
| union REGS r; |
| |
| r.w.ax = 0x800; /* DPMI map physical to linear */ |
| r.w.bx = physAddr >> 16; |
| r.w.cx = physAddr & 0xFFFF; |
| r.w.si = limit >> 16; |
| r.w.di = limit & 0xFFFF; |
| int386(0x31, &r, &r); |
| if (r.x.cflag) |
| FatalError("DPMI_mapPhysicalToLinear() failed!"); |
| return ((long)r.w.bx << 16) + r.w.cx; |
| } |
| |
| void DPMI_setSelectorBase(int sel,long linAddr) |
| /**************************************************************************** |
| * |
| * Function: DPMI_setSelectorBase |
| * Parameters: sel - Selector to change base address for |
| * linAddr - Linear address used for new base address |
| * |
| * Description: Sets the base address for the specified selector. |
| * |
| ****************************************************************************/ |
| { |
| union REGS r; |
| |
| r.w.ax = 7; /* DPMI set selector base address */ |
| r.w.bx = sel; |
| r.w.cx = linAddr >> 16; |
| r.w.dx = linAddr & 0xFFFF; |
| int386(0x31, &r, &r); |
| if (r.x.cflag) |
| FatalError("DPMI_setSelectorBase() failed!"); |
| } |
| |
| void DPMI_setSelectorLimit(int sel,long limit) |
| /**************************************************************************** |
| * |
| * Function: DPMI_setSelectorLimit |
| * Parameters: sel - Selector to change limit for |
| * limit - Limit-1 for the selector |
| * |
| * Description: Sets the memory limit for the specified selector. |
| * |
| ****************************************************************************/ |
| { |
| union REGS r; |
| |
| r.w.ax = 8; /* DPMI set selector limit */ |
| r.w.bx = sel; |
| r.w.cx = limit >> 16; |
| r.w.dx = limit & 0xFFFF; |
| int386(0x31, &r, &r); |
| if (r.x.cflag) |
| FatalError("DPMI_setSelectorLimit() failed!"); |
| } |
| |
| /*-------------------------- VBE Interface routines -----------------------*/ |
| |
| void FatalError(char *msg) |
| { |
| fprintf(stderr,"%s\n", msg); |
| exit(1); |
| } |
| |
| static void ExitVBEBuf(void) |
| { |
| DPMI_freeRealSeg(VESABuf_sel); |
| } |
| |
| void VBE_initRMBuf(void) |
| /**************************************************************************** |
| * |
| * Function: VBE_initRMBuf |
| * Description: Initialises the VBE transfer buffer in real mode memory. |
| * This routine is called by the VESAVBE module every time |
| * it needs to use the transfer buffer, so we simply allocate |
| * it once and then return. |
| * |
| ****************************************************************************/ |
| { |
| if (!VESABuf_sel) { |
| DPMI_allocRealSeg(VESABuf_len, &VESABuf_sel, &VESABuf_rseg); |
| atexit(ExitVBEBuf); |
| } |
| } |
| |
| void VBE_callESDI(RMREGS *regs, void *buffer, int size) |
| /**************************************************************************** |
| * |
| * Function: VBE_callESDI |
| * Parameters: regs - Registers to load when calling VBE |
| * buffer - Buffer to copy VBE info block to |
| * size - Size of buffer to fill |
| * |
| * Description: Calls the VESA VBE and passes in a buffer for the VBE to |
| * store information in, which is then copied into the users |
| * buffer space. This works in protected mode as the buffer |
| * passed to the VESA VBE is allocated in conventional |
| * memory, and is then copied into the users memory block. |
| * |
| ****************************************************************************/ |
| { |
| RMSREGS sregs; |
| |
| VBE_initRMBuf(); |
| sregs.es = VESABuf_rseg; |
| regs->x.di = 0; |
| _fmemcpy(MK_FP(VESABuf_sel,0),buffer,size); |
| DPMI_int86x(0x10, regs, regs, &sregs); |
| _fmemcpy(buffer,MK_FP(VESABuf_sel,0),size); |
| } |
| |
| int VBE_detect(void) |
| /**************************************************************************** |
| * |
| * Function: VBE_detect |
| * Parameters: vgaInfo - Place to store the VGA information block |
| * Returns: VBE version number, or 0 if not detected. |
| * |
| * Description: Detects if a VESA VBE is out there and functioning |
| * correctly. If we detect a VBE interface we return the |
| * VGAInfoBlock returned by the VBE and the VBE version number. |
| * |
| ****************************************************************************/ |
| { |
| RMREGS regs; |
| unsigned short *p1,*p2; |
| VBE_vgaInfo vgaInfo; |
| |
| /* Put 'VBE2' into the signature area so that the VBE 2.0 BIOS knows |
| * that we have passed a 512 byte extended block to it, and wish |
| * the extended information to be filled in. |
| */ |
| strncpy(vgaInfo.VESASignature,"VBE2",4); |
| |
| /* Get the SuperVGA Information block */ |
| regs.x.ax = 0x4F00; |
| VBE_callESDI(®s, &vgaInfo, sizeof(VBE_vgaInfo)); |
| if (regs.x.ax != 0x004F) |
| return 0; |
| if (strncmp(vgaInfo.VESASignature,"VESA",4) != 0) |
| return 0; |
| |
| /* Now that we have detected a VBE interface, copy the list of available |
| * video modes into our local buffer. We *must* copy this mode list, |
| * since the VBE will build the mode list in the VBE_vgaInfo buffer |
| * that we have passed, so the next call to the VBE will trash the |
| * list of modes. |
| */ |
| printf("videomodeptr %x\n",vgaInfo.VideoModePtr); |
| p1 = LfbMapRealPointer(vgaInfo.VideoModePtr); |
| p2 = modeList; |
| while (*p1 != -1) |
| { |
| printf("found mode %x\n",*p1); |
| *p2++ = *p1++; |
| } |
| *p2 = -1; |
| return vgaInfo.VESAVersion; |
| } |
| |
| int VBE_getModeInfo(int mode,VBE_modeInfo *modeInfo) |
| /**************************************************************************** |
| * |
| * Function: VBE_getModeInfo |
| * Parameters: mode - VBE mode to get information for |
| * modeInfo - Place to store VBE mode information |
| * Returns: 1 on success, 0 if function failed. |
| * |
| * Description: Obtains information about a specific video mode from the |
| * VBE. You should use this function to find the video mode |
| * you wish to set, as the new VBE 2.0 mode numbers may be |
| * completely arbitrary. |
| * |
| ****************************************************************************/ |
| { |
| RMREGS regs; |
| |
| regs.x.ax = 0x4F01; /* Get mode information */ |
| regs.x.cx = mode; |
| VBE_callESDI(®s, modeInfo, sizeof(VBE_modeInfo)); |
| if (regs.x.ax != 0x004F) |
| return 0; |
| if ((modeInfo->ModeAttributes & vbeMdAvailable) == 0) |
| return 0; |
| return 1; |
| } |
| |
| void VBE_setVideoMode(int mode) |
| /**************************************************************************** |
| * |
| * Function: VBE_setVideoMode |
| * Parameters: mode - VBE mode number to initialise |
| * |
| ****************************************************************************/ |
| { |
| RMREGS regs; |
| regs.x.ax = 0x4F02; |
| regs.x.bx = mode; |
| DPMI_int86(0x10,®s,®s); |
| } |
| |
| /*-------------------- Application specific routines ----------------------*/ |
| |
| void *GetPtrToLFB(long physAddr) |
| /**************************************************************************** |
| * |
| * Function: GetPtrToLFB |
| * Parameters: physAddr - Physical memory address of linear framebuffer |
| * Returns: Far pointer to the linear framebuffer memory |
| * |
| ****************************************************************************/ |
| { |
| int sel; |
| long linAddr,limit = (4096 * 1024) - 1; |
| |
| // sel = DPMI_allocSelector(); |
| linAddr = DPMI_mapPhysicalToLinear(physAddr,limit); |
| // DPMI_setSelectorBase(sel,linAddr); |
| // DPMI_setSelectorLimit(sel,limit); |
| // return MK_FP(sel,0); |
| return (void*)linAddr; |
| } |
| |
| void AvailableModes(void) |
| /**************************************************************************** |
| * |
| * Function: AvailableModes |
| * |
| * Description: Display a list of available LFB mode resolutions. |
| * |
| ****************************************************************************/ |
| { |
| unsigned short *p; |
| VBE_modeInfo modeInfo; |
| |
| printf("Usage: LFBPROF <xres> <yres>\n\n"); |
| printf("Available 256 color video modes:\n"); |
| for (p = modeList; *p != -1; p++) { |
| if (VBE_getModeInfo(*p, &modeInfo)) { |
| /* Filter out only 8 bit linear framebuffer modes */ |
| if ((modeInfo.ModeAttributes & vbeMdLinear) == 0) |
| continue; |
| if (modeInfo.MemoryModel != vbeMemPK |
| || modeInfo.BitsPerPixel != 8 |
| || modeInfo.NumberOfPlanes != 1) |
| continue; |
| printf(" %4d x %4d %d bits per pixel\n", |
| modeInfo.XResolution, modeInfo.YResolution, |
| modeInfo.BitsPerPixel); |
| } |
| } |
| exit(1); |
| } |
| |
| void InitGraphics(int x,int y) |
| /**************************************************************************** |
| * |
| * Function: InitGraphics |
| * Parameters: x,y - Requested video mode resolution |
| * |
| * Description: Initialise the specified video mode. We search through |
| * the list of available video modes for one that matches |
| * the resolution and color depth are are looking for. |
| * |
| ****************************************************************************/ |
| { |
| unsigned short *p; |
| VBE_modeInfo modeInfo; |
| printf("InitGraphics\n"); |
| |
| for (p = modeList; *p != -1; p++) { |
| if (VBE_getModeInfo(*p, &modeInfo)) { |
| /* Filter out only 8 bit linear framebuffer modes */ |
| if ((modeInfo.ModeAttributes & vbeMdLinear) == 0) |
| continue; |
| if (modeInfo.MemoryModel != vbeMemPK |
| || modeInfo.BitsPerPixel != 8 |
| || modeInfo.NumberOfPlanes != 1) |
| continue; |
| if (modeInfo.XResolution != x || modeInfo.YResolution != y) |
| continue; |
| xres = x; |
| yres = y; |
| bytesperline = modeInfo.BytesPerScanLine; |
| imageSize = bytesperline * yres; |
| VBE_setVideoMode(*p | vbeUseLFB); |
| LFBPtr = GetPtrToLFB(modeInfo.PhysBasePtr); |
| return; |
| } |
| } |
| printf("Valid video mode not found\n"); |
| exit(1); |
| } |
| |
| void EndGraphics(void) |
| /**************************************************************************** |
| * |
| * Function: EndGraphics |
| * |
| * Description: Restores text mode. |
| * |
| ****************************************************************************/ |
| { |
| RMREGS regs; |
| printf("EndGraphics\n"); |
| regs.x.ax = 0x3; |
| DPMI_int86(0x10, ®s, ®s); |
| } |
| |
| void ProfileMode(void) |
| /**************************************************************************** |
| * |
| * Function: ProfileMode |
| * |
| * Description: Profiles framebuffer performance for simple screen clearing |
| * and for copying from system memory to video memory (BitBlt). |
| * This routine thrashes the CPU cache by cycling through |
| * enough system memory buffers to invalidate the entire |
| * CPU external cache before re-using the first memory buffer |
| * again. |
| * |
| ****************************************************************************/ |
| { |
| int i,numClears,numBlts,maxImages; |
| long startTicks,endTicks; |
| void *image[10],*dst; |
| printf("ProfileMode\n"); |
| |
| /* Profile screen clearing operation */ |
| startTicks = LfbGetTicks(); |
| numClears = 0; |
| while ((LfbGetTicks() - startTicks) < 182) |
| LfbMemset(LFBPtr,numClears++,imageSize); |
| endTicks = LfbGetTicks(); |
| clearsPerSec = numClears / ((endTicks - startTicks) * 0.054925); |
| clearsMbPerSec = (clearsPerSec * imageSize) / 1048576.0; |
| |
| /* Profile system memory to video memory copies */ |
| maxImages = ((512 * 1024U) / imageSize) + 2; |
| for (i = 0; i < maxImages; i++) { |
| image[i] = malloc(imageSize); |
| if (image[i] == NULL) |
| FatalError("Not enough memory to profile BitBlt!"); |
| memset(image[i],i+1,imageSize); |
| } |
| startTicks = LfbGetTicks(); |
| numBlts = 0; |
| while ((LfbGetTicks() - startTicks) < 182) |
| LfbMemcpy(LFBPtr,image[numBlts++ % maxImages],imageSize); |
| endTicks = LfbGetTicks(); |
| bitBltsPerSec = numBlts / ((endTicks - startTicks) * 0.054925); |
| bitBltsMbPerSec = (bitBltsPerSec * imageSize) / 1048576.0; |
| } |
| |
| void main(int argc, char *argv[]) |
| { |
| if (VBE_detect() < 0x200) |
| FatalError("This program requires VBE 2.0; Please install UniVBE 5.1."); |
| if (argc != 3) |
| AvailableModes(); /* Display available modes */ |
| |
| InitGraphics(atoi(argv[1]),atoi(argv[2])); /* Start graphics */ |
| ProfileMode(); /* Profile the video mode */ |
| EndGraphics(); /* Restore text mode */ |
| |
| printf("Profiling results for %dx%d 8 bits per pixel.\n",xres,yres); |
| printf("%3.2f clears/s, %2.2f Mb/s\n", clearsPerSec, clearsMbPerSec); |
| printf("%3.2f bitBlt/s, %2.2f Mb/s\n", bitBltsPerSec, bitBltsMbPerSec); |
| } |