26 May 2019, 22:58 | #1 |
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; } |
27 May 2019, 20:36 | #2 |
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. |
27 May 2019, 21:38 | #3 |
Amos Basic
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 } |
29 May 2019, 18:05 | #4 |
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.
|
20 June 2019, 20:46 | #5 | |
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:
|
|
20 June 2019, 21:20 | #6 |
son of 68k
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. |
20 June 2019, 21:31 | #7 | |
bye
Join Date: Jun 2016
Location: Some / Where
Posts: 680
|
Quote:
ScreenToFront is calling RethinkDisplay |
|
28 December 2022, 21:32 | #8 | |
Registered User
Join Date: Jul 2017
Location: Oxford
Posts: 104
|
Quote:
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; 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); } 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]; Last edited by emiespo; 29 December 2022 at 00:53. |
|
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 |
|
|