English Amiga Board


Go Back   English Amiga Board > Coders > Coders. Language > Coders. C/C++

 
 
Thread Tools
Old 26 May 2019, 22:58   #1
balrogsoft
Registered User
 
Join Date: May 2006
Location: Spain
Age: 42
Posts: 71
Game development using C and sys functions, double buffer problem

Hi.
I'm working on my game engine again, and I have performance problems when it runs on stock A600 (whithout acceleration) with Kickstart 2.0. For this system the double buffer is implemented doubling the height of screen and performing a ScrollVPort to the actual frame. When I use this trick, the frame rate drops to the 50%, 25 fps only.

Any idea about why this problem occurs? If I don't use double buffering, I have tried also a basic example blitting a tilemap with vertical scroll from my engine, and there isn't enough time every frame to draw it using amigaos functions, and it only makes a few blittings every frame, it blit the next tile row meanwhile the scroll is running, not a complete row, I must forget something, it's a simple tilemap.

Code:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <exec/execbase.h>
#include <exec/memory.h>  
#include <cybergraphx/cybergraphics.h>
#include <graphics/gfxbase.h>
#include <devices/timer.h>

#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <proto/cybergraphics.h>
#include <proto/timer.h>   

#define WIDTH 320
#define HEIGHT 256
#define DEPTH 5

extern struct ExecBase *SysBase;
struct GfxBase *GfxBase;
struct Library *CyberGfxBase= NULL;
struct Library *TimerBase;      /* to get at the time comparison functions */
static struct IORequest timereq;

UWORD __chip EmptyPointer[] = {0, 0, 0, 0};

ULONG timer(void)
{
  static struct timeval tt;
  struct timeval a, b;

  GetSysTime(&a);
  b = a;
  SubTime(&b, &tt);
  tt = a;

  return b.tv_secs*1000 + b.tv_micro/1000;
}

int main(void)
{
    struct Screen *screen;
    struct Window *window;
    struct DimensionInfo dimsinfo;

        ULONG mtimer = 0;
        int elapsed=0;
        WORD fps=0;
        WORD fps_val=0;
   
    LONG oscan_height,frame=0,frameOffset=0;
        UBYTE numstr[16];
                        
    OpenDevice("timer.device", 0, &timereq, 0);
        TimerBase = timereq.io_Device;
    GfxBase = (struct GfxBase *) OpenLibrary( "graphics.library", 0 );
                    
    if (CyberGfxBase = OpenLibrary("cybergraphics.library",0))
    {
        CloseLibrary(CyberGfxBase);
    }
    
        
    GetDisplayInfoData(FindDisplayInfo(LORES_KEY), (UBYTE *)&dimsinfo,
                               sizeof(struct DimensionInfo), DTAG_DIMS,
                               NULL);
                               
    oscan_height = dimsinfo.MaxOScan.MaxY - dimsinfo.MaxOScan.MinY + 1;

    screen = OpenScreenTags(NULL,
                           SA_DisplayID, PAL_MONITOR_ID|LORES_KEY,
                           SA_Width,     WIDTH,
                           SA_Height,    oscan_height<<1,
                           SA_Depth,     DEPTH, 
                           SA_Type,      CUSTOMSCREEN,
                           SA_Quiet,     TRUE,
                           SA_Draggable, FALSE,
                           SA_Exclusive, TRUE,
                           SA_Interleaved, TRUE,
                           SA_AutoScroll, FALSE,
                           TAG_DONE);
        
    window = OpenWindowTags(NULL,
                            WA_CustomScreen, screen,
                            WA_Flags, WFLG_BORDERLESS | WFLG_ACTIVATE,
                            WA_IDCMP, 0,
                            TAG_DONE);
        
       
    SetPointer(window, EmptyPointer, 1L, 1L, 0L, 0L);

        elapsed = timer();
    while((*(UBYTE *)0xBFE001) & 0x40)
    {  
        frame ^= 1;
        frameOffset = frame*oscan_height;                 // <--- Comment this line to disable double buffer
                
                fps++;
                        
                elapsed = timer();
                
                mtimer+=elapsed;
                if (mtimer>1000)
                {
                        fps_val = fps;
                        fps = 0;
                        mtimer=mtimer-1000;
                }
                
                sprintf(numstr, "%d fps", fps_val);
                                        
                SetAPen(&screen->RastPort, 31);
                Move(&screen->RastPort, 10, 10+frameOffset);
                Text(&screen->RastPort, numstr, strlen(numstr));
                
        screen->ViewPort.RasInfo->RyOffset = frameOffset; //  <--- Comment this line to disable double buffer
        ScrollVPort(&screen->ViewPort);                   //  <--- Comment this line to disable double buffer
    
        WaitTOF();
    }; 
    
        CloseWindow(window);
    CloseScreen(screen);

    CloseDevice(&timereq);
        
    if(GfxBase)
        CloseLibrary((struct Library *)GfxBase);
        
    return 0;
}
balrogsoft is offline  
Old 27 May 2019, 20:36   #2
balrogsoft
Registered User
 
Join Date: May 2006
Location: Spain
Age: 42
Posts: 71
I got the answer, one view, two bitmaps, creating two copperlist for each bitmap and changing the copper list of the view every frame, as explained here, good reading indeed:


https://wiki.amigaos.net/wiki/Classi...ffered_Display


Here is my implementation of this double buffer technique, it works on AOS 2.0 running at 50 fps.Anyway, any attempt to call ScrollVPort decreases performance at 25 fps, so this technique will not give you 50 fps for games that use scroll, I do not know if this is the best you can get using the system functions.

Code:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <exec/execbase.h>
#include <exec/memory.h>  
#include <graphics/gfxbase.h>
#include <devices/timer.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <proto/timer.h>   


#define WIDTH   320
#define HEIGHT  256*2+64
#define DEPTH   5

extern struct ExecBase *SysBase;
struct GfxBase *GfxBase;
struct Library *CyberGfxBase= NULL;
struct Library *TimerBase;      /* to get at the time comparison functions */
static struct IORequest timereq;

ULONG timer(void)
{
  static struct timeval tt;
  struct timeval a, b;

  GetSysTime(&a);
  b = a;
  SubTime(&b, &tt);
  tt = a;

  return b.tv_secs*1000 + b.tv_micro/1000;
}

int main(void)
{
    struct DimensionInfo dimsinfo;

        struct View view, *oldview=NULL; 
        struct ViewPort viewPort;

        struct BitMap bitMap1;
        struct BitMap bitMap2;
        struct RastPort rastPort1;
        struct RastPort rastPort2;
        struct RastPort *rastPort;

        struct RasInfo rasInfo;

        struct cprlist *LOCpr1;
        struct cprlist *SHCpr1;
        struct cprlist *LOCpr2;
        struct cprlist *SHCpr2;
        
        WORD fps=0;
        WORD fps_val=0;
        ULONG mtimer = 0, elapsed=0, depth;
    LONG oscan_height,frame=0;
        UBYTE numstr[16];


    OpenDevice("timer.device", 0, &timereq, 0);
        TimerBase = timereq.io_Device;
        
    GfxBase = (struct GfxBase *) OpenLibrary( "graphics.library", 0 );
                    
    GetDisplayInfoData(FindDisplayInfo(LORES_KEY), (UBYTE *)&dimsinfo,
                               sizeof(struct DimensionInfo), DTAG_DIMS,
                               NULL);
                               
    oscan_height = dimsinfo.MaxOScan.MaxY - dimsinfo.MaxOScan.MinY + 1;

        oldview = GfxBase->ActiView;

        InitView(&view); 

        InitBitMap(&bitMap1, DEPTH, WIDTH, HEIGHT);
            
        InitBitMap(&bitMap2, DEPTH, WIDTH, HEIGHT);

        for (depth=0; depth<DEPTH; depth++)
        {
            bitMap1.Planes[depth] = (PLANEPTR)AllocRaster(WIDTH, HEIGHT);
            bitMap2.Planes[depth] = (PLANEPTR)AllocRaster(WIDTH, HEIGHT);
        }

        
        InitRastPort(&rastPort1);
        rastPort1.BitMap = &bitMap1;
        SetRast(&rastPort1, 0);
        
        InitRastPort(&rastPort2);
        rastPort2.BitMap = &bitMap2;
        SetRast(&rastPort2, 0);
        
        rasInfo.BitMap = &bitMap1;
        rasInfo.RxOffset = 0;
        rasInfo.RyOffset = 0;
        rasInfo.Next = NULL;

        InitVPort(&viewPort);
        view.ViewPort = &viewPort;
        viewPort.RasInfo = &rasInfo;
        viewPort.DWidth = WIDTH;
        viewPort.DHeight = HEIGHT;

        MakeVPort(&view, &viewPort);

        MrgCop(&view);
        
        LOCpr1 = view.LOFCprList;
        SHCpr1 = view.SHFCprList;
        
        
        view.LOFCprList = 0;
        view.SHFCprList = 0;
        
        rasInfo.BitMap = &bitMap2;
        

        MakeVPort(&view, &viewPort);

        MrgCop(&view);
        
        LOCpr2 = view.LOFCprList;
        SHCpr2 = view.SHFCprList;
        
        LoadView(&view);

        elapsed = timer();
    while((*(UBYTE *)0xBFE001) & 0x40)
    {  
        WaitTOF();
                if (frame==0) {
                    view.LOFCprList = LOCpr1;
                    view.SHFCprList = SHCpr1;
                    rastPort = &rastPort1;
                }
                else {
                    view.LOFCprList = LOCpr2;
                    view.SHFCprList = SHCpr2;
                    rastPort = &rastPort2;
                }
                        
                elapsed = timer();
                
                mtimer+=elapsed;
                if (mtimer>1000)
                {
                        fps_val = fps;
                        fps = 0;
                        mtimer = mtimer - 1000;
                }
                
                sprintf(numstr, "%d fps", fps_val);
                                        
                SetAPen(rastPort, 31);
                Move(rastPort, 10, 10);
                Text(rastPort, numstr, strlen(numstr));
                
                LoadView(&view);
                
        frame ^= 1;
                fps++;
          
    }; 

        LoadView(oldview);
        
        WaitTOF();
        
        FreeCprList(LOCpr1);
        FreeCprList(LOCpr2);
        FreeCprList(SHCpr1);
        FreeCprList(SHCpr2);
        
        FreeVPortCopLists(&viewPort); 

        for(depth=0; depth<DEPTH; depth++)
        {
            if (bitMap1.Planes[depth])
                FreeRaster(bitMap1.Planes[depth], WIDTH, HEIGHT);
            if (bitMap2.Planes[depth])
                FreeRaster(bitMap2.Planes[depth], WIDTH, HEIGHT);
        }
    CloseDevice(&timereq);
        
        
    if(GfxBase)
        CloseLibrary((struct Library *)GfxBase);
        
    return 0;
}

Last edited by balrogsoft; 27 May 2019 at 21:27.
balrogsoft is offline  
Old 27 May 2019, 21:38   #3
Astrofra
Amos Basic
 
Astrofra's Avatar
 
Join Date: Feb 2013
Location: Orleans | France
Age: 49
Posts: 85
Ahaha, amazing.

I was about to answer, but you finally found a technique that is far more efficient than everything I tried.

As you probably know, there is no official support for double buffering in AmigaOS 1.3/2.0.

In my own project (an adventure game for the OCS range of Amigas), I'm using the MakeScreen()/RethinkDisplay() technique, but it is known for being the slowest method ever (the autodocs say RethinkDisplay() takes milliseconds).

The ScrollVPort() trick is supposed to be faster, but as the source code of this 3.1 version is approx. 300 lines of asm, so I guess this might be why it is a bit slower than what we expected


for the records, here is my double buffer routine (works on AmigaDOS1.3, but hell, it is SO SLOW) :



Code:
void flipBuffers(buffered_screen *screen)
{
	/* Swap the physical and logical bitmaps */
	screen->physical = (USHORT)(screen->physical)^1;
	screen->screen->RastPort.BitMap	= screen->bitmaps[screen->physical];
	screen->screen->ViewPort.RasInfo->BitMap = screen->bitmaps[screen->physical];
#ifdef DEBUG_MACROS	
	printf("flipBuffers() : physical screen: %d\n", screen->physical);
	printf("flipBuffers() : logical screen: %d\n", getLogicalBitmapIndex(screen));
#endif	
}

void presentScreen(buffered_screen *screen)
{
	/* Update the physical display to match the recently updated bitmap. */
	MakeScreen(screen->screen);
	RethinkDisplay();
#ifdef DEBUG_MACROS	
	printf("presentScreen()\n");
#endif	
}
Astrofra is offline  
Old 29 May 2019, 18:05   #4
balrogsoft
Registered User
 
Join Date: May 2006
Location: Spain
Age: 42
Posts: 71
RethinkDisplay is probably one of the worst options for games. My code doesn't run under AmigaOS 1.3, but I will take a look to see if it can work.
balrogsoft is offline  
Old 20 June 2019, 20:46   #5
bebbo
bye
 
Join Date: Jun 2016
Location: Some / Where
Posts: 680
just some additional info:

ScrollVPort(&screen->ViewPort);

is calling

WaitBOVP(&screen->ViewPort);

so a WaitTOF(); after that might be the cause to skip a frame.

And there is this comment to WaitBOVP(&screen->ViewPort);:

Quote:
BUGS
Horrors! This function currently busy waits waiting for the
beam to get to the right place. It should use the copper
interrupt to trigger and send signals like WaitTOF does.
=> ScrollVPort(&screen->ViewPort); is a bad idea too
bebbo is offline  
Old 20 June 2019, 21:20   #6
meynaf
son of 68k
 
meynaf's Avatar
 
Join Date: Nov 2007
Location: Lyon / France
Age: 51
Posts: 5,323
Alternatively it's possible to open two screens and perform the frame flipping with ScreenToFront -- if the game does not need to multitask.

But under OS i would rather use a back-buffer (i.e. mem copy to do the refresh). This is better for performance if only part of the screen needs updating.
meynaf is offline  
Old 20 June 2019, 21:31   #7
bebbo
bye
 
Join Date: Jun 2016
Location: Some / Where
Posts: 680
Quote:
Originally Posted by meynaf View Post
Alternatively it's possible to open two screens and perform the frame flipping with ScreenToFront -- if the game does not need to multitask.

But under OS i would rather use a back-buffer (i.e. mem copy to do the refresh). This is better for performance if only part of the screen needs updating.

ScreenToFront is calling RethinkDisplay
bebbo is offline  
Old 28 December 2022, 21:32   #8
emiespo
Registered User
 
Join Date: Jul 2017
Location: Oxford
Posts: 104
Quote:
Originally Posted by balrogsoft View Post
I got the answer, one view, two bitmaps, creating two copperlist for each bitmap and changing the copper list of the view every frame, as explained here, good reading indeed:
Hi all, I can confirm this technique also works on 1.3, however I have a slightly more complex use-case... I'm trying to write a CDXL player, so it means I need to also change the palette with each frame.

And, of course, this doesn't seem to be working (not to mention that audio also is very hard to keep in sync with audio.device, but this is a separate topic).

Before I go back to killing the OS (seems the only viable alternative), is there anything I might be missing?

This is roughly the main loop:

Code:
    // Use the currently loaded buffer
    struct CdxlFrame* frame = animData->frame[currentBuffer];
    // start playing the audio.
    playSample(frame->audioData, frame->header->cdxl_frequency == 0 ? 11025 : frame->header->cdxl_frequency, frame->header->cdxl_audioSize);
    WaitTOF();
    swapBuffers(&screenData->dblBufferData, currentBuffer,  frame->paletteData);

    // read next frame while we wait for sound to finish playing
    readFrame(animData, currentBuffer);
    waitSampleEnd();
    currentBuffer ^= 1;
the swap buffers function simply swaps the copper lists and loads new colours:

Code:
inline void swapBuffers(struct DoubleBufferGfxData* dblBufferData, UWORD buffer, UWORD* palette) {
    struct View* view = &dblBufferData->view;
    view->LOFCprList = dblBufferData->LOFCpr[buffer];
    LoadRGB4(&dblBufferData->viewPort, palette, 16);
    LoadView(view);
}
...however colours are not being reloaded as expected with this method. I assume this is because they are somewhat associated to the initial copper list. It seems that they *do change* for some frames, but the rest have wrong colours (resulting in the dreaded HAM fringes).

Is there a way to have proper colour reload? Maybe using custom copper lists? (which basically would get me closer to not using the OS...).

EDIT: it seems that investigating the ViewPort structure led me to the solution. I added this to the code above:

Code:
viewPort->DspIns = dblBufferData->dspIns[buffer];
and now LoadRGB4() would actually load colours as expected. Fun as before trying this "faster" approach I tried with RethinkDisplay() and although I could see the colours updating, I would also see annoying flickering.

Last edited by emiespo; 29 December 2022 at 00:53.
emiespo is offline  
 


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools

Similar Threads
Thread Thread Starter Forum Replies Last Post
Request: Long double (80bit) functions in x86/x64 assembly for WinUAE Toni Wilen request.Other 2 20 April 2018 08:50
ScrollVPort double buffer problem with garbage pixels balrogsoft Coders. General 5 29 May 2014 12:31
Double buffer copper?? h0ffman Coders. General 8 19 July 2011 19:10
Vsync Fullscreen and Double Buffer, incorrect frame rate? rsn8887 support.WinUAE 1 07 April 2011 20:43
Big Brother 0.51 Non-Sys Monitor Problem WintermuteX support.WinUAE 6 19 October 2003 16:19

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT +2. The time now is 15:26.

Top

Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.
Page generated in 0.13769 seconds with 13 queries