Help creating Static Polygons for fast drawing
-
- Posts: 28
- Joined: Sat Apr 13, 2013 10:40 pm
Help creating Static Polygons for fast drawing
The polygons created with GLBegin are not static. Static polygons would allow you to set the value and then have it stay constant for the program until it is deleted. I looking for a solution but haven't found any.
Re: Help creating Static Polygons for fast drawing
As far as I know you have to use either vertex arrays or displaylists. If you want to increase speed you will have to simply draw less on the screen.
-
- Posts: 28
- Joined: Sat Apr 13, 2013 10:40 pm
Re: Help creating Static Polygons for fast drawing
Which one of those wouldn't cause a stack overflow at ~5000 polys since instructions have to fit in Cache and the Verticies alone would be 60KB which would be hard to fit in the Wii's L2 Cache.
Re: Help creating Static Polygons for fast drawing
Vertex groups, split the model you wish to render into groups and each group can be a list.
Not the most elegant solution but it does get around your issue.
Not the most elegant solution but it does get around your issue.
-
- Posts: 28
- Joined: Sat Apr 13, 2013 10:40 pm
Re: Help creating Static Polygons for fast drawing
Can you give an example of how you would set up the makefile and the source code for vertex groups.
Re: Help creating Static Polygons for fast drawing
It can be done via a model editor, each group (called an object in blender) is part of the model, you just have to handle that in your loader code. Each "object" is loaded into a display list and called in the order specified by the model, the code is really dependent on the format and posting an example wouldn't do you any good since it may not work with the format you want.
But the basic flow is:
Load model storing the vertex group (probably using std::vector, or some custom linked list to handle parenting)
Load each group into a display list.
Iterate through and call each display list.
This this kind of setup you can hide certain parts of the model that aren't being used currently, Nintendo uses this extensively for things like hands.
But the basic flow is:
Load model storing the vertex group (probably using std::vector, or some custom linked list to handle parenting)
Load each group into a display list.
Iterate through and call each display list.
This this kind of setup you can hide certain parts of the model that aren't being used currently, Nintendo uses this extensively for things like hands.
-
- Posts: 28
- Joined: Sat Apr 13, 2013 10:40 pm
Re: Help creating Static Polygons for fast drawing
The attached file is me trying to get display lists which are like vertex array with DevkitPPC. Do you see a mistake? Is that not vertex groups? I see it has a FIFO limit of 8192KB.
/*---------------------------------------------------------------------------------
nehe lesson 5 port to GX by WinterMute
---------------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <math.h>
#include <gccore.h>
#include <wiiuse/wpad.h>
#define DEFAULT_FIFO_SIZE (256*1024)
static void *frameBuffer[2] = { NULL, NULL};
GXRModeObj *rmode;
//---------------------------------------------------------------------------------
int main( int argc, char **argv ){
//---------------------------------------------------------------------------------
f32 yscale;
u32 xfbHeight;
Mtx view;
Mtx44 perspective;
Mtx model, modelview;
float rtri = 0.0f , rquad = 0.0f;
u32 fb = 0; // initial framebuffer index
GXColor background = {0, 0, 0, 0xff};
// init the vi.
VIDEO_Init();
WPAD_Init();
rmode = VIDEO_GetPreferredMode(NULL);
// allocate 2 framebuffers for double buffering
frameBuffer[0] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
frameBuffer[1] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
VIDEO_Configure(rmode);
VIDEO_SetNextFramebuffer(frameBuffer[fb]);
VIDEO_SetBlack(FALSE);
VIDEO_Flush();
VIDEO_WaitVSync();
if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync();
// setup the fifo and then init the flipper
void *gp_fifo = NULL;
gp_fifo = memalign(32,DEFAULT_FIFO_SIZE);
memset(gp_fifo,0,DEFAULT_FIFO_SIZE);
GX_Init(gp_fifo,DEFAULT_FIFO_SIZE);
// clears the bg to color and clears the z buffer
GX_SetCopyClear(background, 0x00ffffff);
// other gx setup
GX_SetViewport(0,0,rmode->fbWidth,rmode->efbHeight,0,1);
yscale = GX_GetYScaleFactor(rmode->efbHeight,rmode->xfbHeight);
xfbHeight = GX_SetDispCopyYScale(yscale);
GX_SetScissor(0,0,rmode->fbWidth,rmode->efbHeight);
GX_SetDispCopySrc(0,0,rmode->fbWidth,rmode->efbHeight);
GX_SetDispCopyDst(rmode->fbWidth,xfbHeight);
GX_SetCopyFilter(rmode->aa,rmode->sample_pattern,GX_TRUE,rmode->vfilter);
GX_SetFieldMode(rmode->field_rendering,((rmode->viHeight==2*rmode->xfbHeight)?GX_ENABLE:GX_DISABLE));
GX_SetCullMode(GX_CULL_NONE);
GX_CopyDisp(frameBuffer[fb],GX_TRUE);
GX_SetDispCopyGamma(GX_GM_1_0);
// setup the vertex descriptor
// tells the flipper to expect direct data
GX_ClearVtxDesc();
GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
// setup the vertex attribute table
// describes the data
// args: vat location 0-7, type of data, data format, size, scale
// so for ex. in the first call we are sending position data with
// 3 values X,Y,Z of size F32. scale sets the number of fractional
// bits for non float data.
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGB8, 0);
GX_SetNumChans(1);
GX_SetNumTexGens(0);
GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORDNULL, GX_TEXMAP_NULL, GX_COLOR0A0);
GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR);
// setup our camera at the origin
// looking down the -z axis with y up
guVector cam = {0.0F, 0.0F, 0.0F},
up = {0.0F, 1.0F, 0.0F},
look = {0.0F, 0.0F, -1.0F};
guLookAt(view, &cam, &up, &look);
// setup our projection matrix
// this creates a perspective matrix with a view angle of 90,
// and aspect ratio based on the display resolution
f32 w = rmode->viWidth;
f32 h = rmode->viHeight;
guPerspective(perspective, 45, (f32)w/h, 0.1F, 300.0F);
GX_LoadProjectionMtx(perspective, GX_PERSPECTIVE);
guVector triAxis = {0,1,0};
guVector cubeAxis = {1,1,1};
while(1) {
WPAD_ScanPads();
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_HOME) exit(0);
// do this before drawing
GX_SetViewport(0,0,rmode->fbWidth,rmode->efbHeight,0,1);
guMtxIdentity(model);
guMtxRotAxisDeg(model, &triAxis, rtri);
guMtxTransApply(model, model, -4.5f,0.0f,-6.0f);
guMtxConcat(view,model,modelview);
// load the modelview matrix into matrix memory
GX_LoadPosMtxImm(modelview, GX_PNMTX0);
GX_Begin(GX_TRIANGLES, GX_VTXFMT0, 12); // Draw A Pyramid
GX_Position3f32( 0.0f, 1.0f, 0.0f); // Top of Triangle (front)
GX_Color3f32(1.0f,0.0f,0.0f); // Set The Color To Red
GX_Position3f32(-1.0f,-1.0f, 1.0f); // Left of Triangle (front)
GX_Color3f32(0.0f,1.0f,0.0f); // Set The Color To Green
GX_Position3f32( 1.0f,-1.0f, 1.0f); // Right of Triangle (front)
GX_Color3f32(0.0f,0.0f,1.0f); // Set The Color To Blue
GX_Position3f32( 0.0f, 1.0f, 0.0f); // Top of Triangle (Right)
GX_Color3f32(1.0f,0.0f,0.0f); // Set The Color To Red
GX_Position3f32( 1.0f,-1.0f, 1.0f); // Left of Triangle (Right)
GX_Color3f32(0.0f,0.0f,1.0f); // Set The Color To Blue
GX_Position3f32( 1.0f,-1.0f,-1.0f); // Right of Triangle (Right)
GX_Color3f32(0.0f,1.0f,0.0f); // Set The Color To Green
GX_Position3f32( 0.0f, 1.0f, 0.0f); // Top of Triangle (Back)
GX_Color3f32(1.0f,0.0f,0.0f); // Set The Color To Red
GX_Position3f32(-1.0f,-1.0f,-1.0f); // Left of Triangle (Back)
GX_Color3f32(0.0f,0.0f,1.0f); // Set The Color To Blue
GX_Position3f32( 1.0f,-1.0f,-1.0f); // Right of Triangle (Back)
GX_Color3f32(0.0f,1.0f,0.0f); // Set The Color To Green
GX_Position3f32( 0.0f, 1.0f, 0.0f); // Top of Triangle (Left)
GX_Color3f32(1.0f,0.0f,0.0f); // Set The Color To Red
GX_Position3f32(-1.0f,-1.0f,-1.0f); // Left of Triangle (Left)
GX_Color3f32(0.0f,0.0f,1.0f); // Set The Color To Blue
GX_Position3f32(-1.0f,-1.0f, 1.0f); // Right of Triangle (Left)
GX_Color3f32(0.0f,1.0f,0.0f); // Set The Color To Green
GX_End();
guMtxIdentity(model);
guMtxRotAxisDeg(model, &cubeAxis, rquad);
guMtxTransApply(model, model, 1.5f,0.0f,-7.0f);
guMtxConcat(view,model,modelview);
// load the modelview matrix into matrix memory
GX_LoadPosMtxImm(modelview, GX_PNMTX0);
....................................
ATTACHED disp list
....................................
// do this stuff after drawing
GX_DrawDone();
fb ^= 1; // flip framebuffer
GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
GX_SetColorUpdate(GX_TRUE);
GX_CopyDisp(frameBuffer[fb],GX_TRUE);
VIDEO_SetNextFramebuffer(frameBuffer[fb]);
VIDEO_Flush();
VIDEO_WaitVSync();
rtri+=0.2f; // Increase The Rotation Variable For The Triangle ( NEW )
rquad-=0; // Decrease The Rotation Variable For The Quad ( NEW )
}
return 0;
}
/*---------------------------------------------------------------------------------
nehe lesson 5 port to GX by WinterMute
---------------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <math.h>
#include <gccore.h>
#include <wiiuse/wpad.h>
#define DEFAULT_FIFO_SIZE (256*1024)
static void *frameBuffer[2] = { NULL, NULL};
GXRModeObj *rmode;
//---------------------------------------------------------------------------------
int main( int argc, char **argv ){
//---------------------------------------------------------------------------------
f32 yscale;
u32 xfbHeight;
Mtx view;
Mtx44 perspective;
Mtx model, modelview;
float rtri = 0.0f , rquad = 0.0f;
u32 fb = 0; // initial framebuffer index
GXColor background = {0, 0, 0, 0xff};
// init the vi.
VIDEO_Init();
WPAD_Init();
rmode = VIDEO_GetPreferredMode(NULL);
// allocate 2 framebuffers for double buffering
frameBuffer[0] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
frameBuffer[1] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
VIDEO_Configure(rmode);
VIDEO_SetNextFramebuffer(frameBuffer[fb]);
VIDEO_SetBlack(FALSE);
VIDEO_Flush();
VIDEO_WaitVSync();
if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync();
// setup the fifo and then init the flipper
void *gp_fifo = NULL;
gp_fifo = memalign(32,DEFAULT_FIFO_SIZE);
memset(gp_fifo,0,DEFAULT_FIFO_SIZE);
GX_Init(gp_fifo,DEFAULT_FIFO_SIZE);
// clears the bg to color and clears the z buffer
GX_SetCopyClear(background, 0x00ffffff);
// other gx setup
GX_SetViewport(0,0,rmode->fbWidth,rmode->efbHeight,0,1);
yscale = GX_GetYScaleFactor(rmode->efbHeight,rmode->xfbHeight);
xfbHeight = GX_SetDispCopyYScale(yscale);
GX_SetScissor(0,0,rmode->fbWidth,rmode->efbHeight);
GX_SetDispCopySrc(0,0,rmode->fbWidth,rmode->efbHeight);
GX_SetDispCopyDst(rmode->fbWidth,xfbHeight);
GX_SetCopyFilter(rmode->aa,rmode->sample_pattern,GX_TRUE,rmode->vfilter);
GX_SetFieldMode(rmode->field_rendering,((rmode->viHeight==2*rmode->xfbHeight)?GX_ENABLE:GX_DISABLE));
GX_SetCullMode(GX_CULL_NONE);
GX_CopyDisp(frameBuffer[fb],GX_TRUE);
GX_SetDispCopyGamma(GX_GM_1_0);
// setup the vertex descriptor
// tells the flipper to expect direct data
GX_ClearVtxDesc();
GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
// setup the vertex attribute table
// describes the data
// args: vat location 0-7, type of data, data format, size, scale
// so for ex. in the first call we are sending position data with
// 3 values X,Y,Z of size F32. scale sets the number of fractional
// bits for non float data.
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGB8, 0);
GX_SetNumChans(1);
GX_SetNumTexGens(0);
GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORDNULL, GX_TEXMAP_NULL, GX_COLOR0A0);
GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR);
// setup our camera at the origin
// looking down the -z axis with y up
guVector cam = {0.0F, 0.0F, 0.0F},
up = {0.0F, 1.0F, 0.0F},
look = {0.0F, 0.0F, -1.0F};
guLookAt(view, &cam, &up, &look);
// setup our projection matrix
// this creates a perspective matrix with a view angle of 90,
// and aspect ratio based on the display resolution
f32 w = rmode->viWidth;
f32 h = rmode->viHeight;
guPerspective(perspective, 45, (f32)w/h, 0.1F, 300.0F);
GX_LoadProjectionMtx(perspective, GX_PERSPECTIVE);
guVector triAxis = {0,1,0};
guVector cubeAxis = {1,1,1};
while(1) {
WPAD_ScanPads();
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_HOME) exit(0);
// do this before drawing
GX_SetViewport(0,0,rmode->fbWidth,rmode->efbHeight,0,1);
guMtxIdentity(model);
guMtxRotAxisDeg(model, &triAxis, rtri);
guMtxTransApply(model, model, -4.5f,0.0f,-6.0f);
guMtxConcat(view,model,modelview);
// load the modelview matrix into matrix memory
GX_LoadPosMtxImm(modelview, GX_PNMTX0);
GX_Begin(GX_TRIANGLES, GX_VTXFMT0, 12); // Draw A Pyramid
GX_Position3f32( 0.0f, 1.0f, 0.0f); // Top of Triangle (front)
GX_Color3f32(1.0f,0.0f,0.0f); // Set The Color To Red
GX_Position3f32(-1.0f,-1.0f, 1.0f); // Left of Triangle (front)
GX_Color3f32(0.0f,1.0f,0.0f); // Set The Color To Green
GX_Position3f32( 1.0f,-1.0f, 1.0f); // Right of Triangle (front)
GX_Color3f32(0.0f,0.0f,1.0f); // Set The Color To Blue
GX_Position3f32( 0.0f, 1.0f, 0.0f); // Top of Triangle (Right)
GX_Color3f32(1.0f,0.0f,0.0f); // Set The Color To Red
GX_Position3f32( 1.0f,-1.0f, 1.0f); // Left of Triangle (Right)
GX_Color3f32(0.0f,0.0f,1.0f); // Set The Color To Blue
GX_Position3f32( 1.0f,-1.0f,-1.0f); // Right of Triangle (Right)
GX_Color3f32(0.0f,1.0f,0.0f); // Set The Color To Green
GX_Position3f32( 0.0f, 1.0f, 0.0f); // Top of Triangle (Back)
GX_Color3f32(1.0f,0.0f,0.0f); // Set The Color To Red
GX_Position3f32(-1.0f,-1.0f,-1.0f); // Left of Triangle (Back)
GX_Color3f32(0.0f,0.0f,1.0f); // Set The Color To Blue
GX_Position3f32( 1.0f,-1.0f,-1.0f); // Right of Triangle (Back)
GX_Color3f32(0.0f,1.0f,0.0f); // Set The Color To Green
GX_Position3f32( 0.0f, 1.0f, 0.0f); // Top of Triangle (Left)
GX_Color3f32(1.0f,0.0f,0.0f); // Set The Color To Red
GX_Position3f32(-1.0f,-1.0f,-1.0f); // Left of Triangle (Left)
GX_Color3f32(0.0f,0.0f,1.0f); // Set The Color To Blue
GX_Position3f32(-1.0f,-1.0f, 1.0f); // Right of Triangle (Left)
GX_Color3f32(0.0f,1.0f,0.0f); // Set The Color To Green
GX_End();
guMtxIdentity(model);
guMtxRotAxisDeg(model, &cubeAxis, rquad);
guMtxTransApply(model, model, 1.5f,0.0f,-7.0f);
guMtxConcat(view,model,modelview);
// load the modelview matrix into matrix memory
GX_LoadPosMtxImm(modelview, GX_PNMTX0);
....................................
ATTACHED disp list
....................................
// do this stuff after drawing
GX_DrawDone();
fb ^= 1; // flip framebuffer
GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
GX_SetColorUpdate(GX_TRUE);
GX_CopyDisp(frameBuffer[fb],GX_TRUE);
VIDEO_SetNextFramebuffer(frameBuffer[fb]);
VIDEO_Flush();
VIDEO_WaitVSync();
rtri+=0.2f; // Increase The Rotation Variable For The Triangle ( NEW )
rquad-=0; // Decrease The Rotation Variable For The Quad ( NEW )
}
return 0;
}
- Attachments
-
- Answers.zip
- (64.42 KiB) Downloaded 1026 times
-
- Posts: 28
- Joined: Sat Apr 13, 2013 10:40 pm
Re: Help creating Static Polygons for fast drawing
I've figured out that its the size of the object file I'm compiling. It's probably to big for the wii's l2 cache so the linker hangs. Although is 32 bit aligned a cast to u32 u8 or u16 or something else this may also be an issue. Ultimately I want to know that and how to set up the makefile for multiple object files with a main file.
-
- Site Admin
- Posts: 1986
- Joined: Tue Aug 09, 2005 3:21 am
- Location: UK
- Contact:
Re: Help creating Static Polygons for fast drawing
The Wii L2 has no bearing on the linker.
Many of the examples we provide contain embedded data or arrays for the sake of simplicity but there are limits to how useful that approach is when it comes to more complex projects. Compiling large datasets as C arrays will eventually make the compiler or the linker fall over once you reach a certain size.
The Makefiles already handle multiple object files - all you have to do is create a new source file in the source directory & the Makefile does the rest. Ideally you should be using some kind of binary format for models and either embedding those or loading them from the filesystem at runtime.
Embedding datafiles is fairly easy. At the top of the Makefile is this section :-
You can either add a list of the directories containing binary files to the DATA variable or add a new variable (say MODELS := )
If you add a new variable then you'll need to modify the VPATH code
add a \ to that last line and $(foreach dir,$(MODELS),$(CURDIR)/$(dir)) on the next line. Obviously change MODELS to whatever variable you used.
In the section to build object files you'll need to add code to pick up whatever extension you use
so add something like MODELFILES := $(foreach dir,$(MODELS),$(notdir $(wildcard $(dir)/*.mdl))) unless you just chose to add them to the DATA variable which picks up everything regardless of extension.
If you're using your own variable and file extension then you'll need to modify OFILES
In your C file you'll need to include the generated header so
Again <filename> is the name of the file that was embedded as binary. <filename>_mdl will hold the address of the start of the data, <filename>_mdl_end is the end address & _size gives you the size in bytes. You'll probably need to cast the variables appropriately.
Many of the examples we provide contain embedded data or arrays for the sake of simplicity but there are limits to how useful that approach is when it comes to more complex projects. Compiling large datasets as C arrays will eventually make the compiler or the linker fall over once you reach a certain size.
The Makefiles already handle multiple object files - all you have to do is create a new source file in the source directory & the Makefile does the rest. Ideally you should be using some kind of binary format for models and either embedding those or loading them from the filesystem at runtime.
Embedding datafiles is fairly easy. At the top of the Makefile is this section :-
Code: Select all
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# INCLUDES is a list of directories containing extra header files
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
BUILD := build
SOURCES := source
DATA :=
TEXTURES := textures
INCLUDES :=
If you add a new variable then you'll need to modify the VPATH code
Code: Select all
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir)) \
$(foreach dir,$(TEXTURES),$(CURDIR)/$(dir))
In the section to build object files you'll need to add code to pick up whatever extension you use
Code: Select all
#---------------------------------------------------------------------------------
# automatically build a list of object files for our project
#---------------------------------------------------------------------------------
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
sFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.S)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
SCFFILES := $(foreach dir,$(TEXTURES),$(notdir $(wildcard $(dir)/*.scf)))
TPLFILES := $(SCFFILES:.scf=.tpl)
If you're using your own variable and file extension then you'll need to modify OFILES
Code: Select all
export OFILES := $(addsuffix .o,$(BINFILES)) \
$(addsuffix .o,$(TPLFILES)) \
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) \
$(sFILES:.s=.o) $(SFILES:.S=.o)[code]
add a \ to the end of the last line & $(addsuffix .o,$(MODELFILES)) on the next line.
Then add a rule for your extension like this
[code]#---------------------------------------------------------------------------------
# This rule links in binary data with the .mdl extension
#---------------------------------------------------------------------------------
%.mdl.o : %.mdl
#---------------------------------------------------------------------------------
@echo $(notdir $<)
$(bin2o)
Code: Select all
#include "<filename>_mdl_h"[code] where <filename> is the name of the binary file without the extension.
The header will define several variables as follows
[code]extern const u8 <filename>_mdl_end[];
extern const u8 <filename>_mdl[];
extern const u32 <filename>_mdl_size;
-
- Posts: 28
- Joined: Sat Apr 13, 2013 10:40 pm
Re: Help creating Static Polygons for fast drawing
How would I store TEV calculations to be used as spherical unit vectors if i can't use arrays. If I have lots of TEV calculations wouldn't I need an array? Also linked list has O(n) traverse time so why wouldn't I use a binary array which stores by memory addresses?
Who is online
Users browsing this forum: No registered users and 4 guests