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(&regs, &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(&regs, 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,&regs,&regs);
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, &regs, &regs);
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