Slick Forums

Discuss the Slick 2D Library
It is currently Sun May 26, 2013 3:36 am

All times are UTC




Post new topic Reply to topic  [ 11 posts ] 
Author Message
PostPosted: Thu Mar 15, 2012 4:10 pm 
Offline
Oldbie

Joined: Thu Mar 15, 2012 12:38 am
Posts: 261
Hello,

I have recently discovered Slick2D and I like it so far.

I am creating a Space 4X game and currently use Swing/Java2D. I want to get better performance so I am in the process of investigating Slick in hopes of performing a partial conversion. Because I have already progressed so far along, I cannot completely rewrite the game. What I can do is to put Slick2D into the game and use it for rendering a few portions of it: namely the main view of the game and combat. This will allow me to use Slick and keep my GUI in tact. Not a perfect solution, but the best I can do now.

I do have some questions, though. I have noticed that Slick2D is quite similar to Java2D and I'm relieved.
I used the AffineTransform in Java2D extensively, and I have noticed that Slick2D does not offer any inverse transform methods?
For example, I use these methods quite a bit for various reasons:
Code:
AffineTransform    createInverse()
Shape    createTransformedShape(Shape pSrc)
Point2D    inverseTransform(Point2D ptSrc, Point2D ptDst)
void    setToIdentity()

I do not see these on the Transform class on the javadoc for Slick2D:
http://slick.cokeandcode.com/javadoc/or ... sform.html

So I will have to implement them myself?


On Swing + Slick2D, has anyone really dived deep in this? My proof of concept works reasonably well. There is just an odd issue that if I make the render area of the CanvasGameContainer take up the entire screen, it will crash, so I had to make it 1 less pixel than the displayable resolution. I do get good performance, though: I can draw 3,700 rotating and scaled transparent images at 60 FPS (I could only get 30 FPS on Java2D). Depending on the scale, this could increase to 4,700 images at 60 FPS. This pleases me, so I think I will get huge performance gains over Java2D (Obviously!).

Finally, on the OpenGL aspect. I heard that not all machines have these libraries (??). Is this true? I haven't done much with hardware rendering capabilities (I've always used Java2D) so this is new ground for me. Can I distribute the OpenGL libraries with my application and have an installer install them if required?

I have some more questions, but I'll stop here.

Thanks.


Top
 Profile  
 
PostPosted: Thu Mar 15, 2012 5:13 pm 
Offline
Slick Zombie

Joined: Sat Jan 27, 2007 7:10 pm
Posts: 1469
Quote:
I used the AffineTransform in Java2D extensively, and I have noticed that Slick2D does not offer any inverse transform methods?

I'm not too familiar with AffineTransform or inverseTransform, but usually there are simpler ways of doing this with Slick. For example, to rotate a shape you could simply rotate the graphics context:
Code:
g.rotate(cx, cy, angle);
g.draw(...);
g.rotate(cx, cy, -angle);


Slick also provides "pushTransform" and "popTransform" which can be useful.

Quote:
On Swing + Slick2D, has anyone really dived deep in this?

Generally speaking, it's not a good idea for a game. Swing is slow, clunky and bloated, and doesn't play well with OpenGL. Most people use this mix instead to create toolsets and editors aimed at developers (for example, Slick's Hiero font editor uses it well).

But if you just want to make something that "works" in a short amount of time, and don't plan to learn any new GUI libraries, then I suppose you can stick to Swing.

Quote:
There is just an odd issue that if I make the render area of the CanvasGameContainer take up the entire screen, it will crash, so I had to make it 1 less pixel than the displayable resolution.

This sounds like a bug. First try updating your libraries (lwjgl.jar and the native libraries) to the latest version of LWJGL. If it persists you can post a small test-case in the BUG/RFE forum.

Quote:
I do get good performance, though: I can draw 3,700 rotating and scaled transparent images at 60 FPS (I could only get 30 FPS on Java2D). Depending on the scale, this could increase to 4,700 images at 60 FPS. This pleases me, so I think I will get huge performance gains over Java2D (Obviously!).

If you plan on rendering that many images, there are lots of optimizations discussed in this thread:
viewtopic.php?p=26686#p26686 (I am getting 17,000+ images at 60 FPS)

Don't get too caught up thinking about "how it would be done in Java2D," as that line of thought will only slow you down. e.g. Double-buffering your entire scene is unnecessary.

Quote:
Finally, on the OpenGL aspect. I heard that not all machines have these libraries (??). Is this true? I haven't done much with hardware rendering capabilities (I've always used Java2D) so this is new ground for me. Can I distribute the OpenGL libraries with my application and have an installer install them if required?

You should always be distributing the LWJGL native libraries along with your program. There are a lot of tools that make it useful to package an LWJGL application into a single JAR/executable.

If OpenGL is not supported on the computer, then your game will simply not run. This is rather unlikely, though; most people looking to play computer games will have a graphics card that supports OpenGL at some level. Unless you are targeting ancient systems, you shouldn't worry too much about this. If compatibility is your top concern, either abstract your rendering (a lot of work for very little gain) or stick purely to Java2D. But, realistically, the number of users with no OpenGL support is minuscule.

If you have more questions feel free to ask.


Top
 Profile  
 
PostPosted: Thu Mar 15, 2012 5:35 pm 
Offline
Oldbie

Joined: Thu Mar 15, 2012 12:38 am
Posts: 261
davedes wrote:
I'm not too familiar with AffineTransform or inverseTransform, but usually there are simpler ways of doing this with Slick. For example, to rotate a shape you could simply rotate the graphics context:
Code:
g.rotate(cx, cy, angle);
g.draw(...);
g.rotate(cx, cy, -angle);


Slick also provides "pushTransform" and "popTransform" which can be useful.


Thanks!

davedes wrote:
Generally speaking, it's not a good idea for a game. Swing is slow, clunky and bloated, and doesn't play well with OpenGL. Most people use this mix instead to create toolsets and editors aimed at developers (for example, Slick's Hiero font editor uses it well).

But if you just want to make something that "works" in a short amount of time, and don't plan to learn any new GUI libraries, then I suppose you can stick to Swing.


Can you talk a little more about how Swing and OpenGL do not behave well with each other? What sort of problems happen and so on? Are there any showstoppers? In my proof of concept, I haven't really seen anything except that one instance of crashing (which as you make mention of, is probably due to out dated libs). But it is just a simple application and uses a couple components/JInternalFrames.

Performance is why I want to switch the way I render the game. Most of the time (90%+), the player is either looking at the main view or a UI window taking up the entire screen.

I need a GUI that has a lot of features and has flexibility. I see people refer to TWL (I think it is called). This is probably best asked the developers, but maybe someone knows and can chim in: Is the feature set for this comparable to Swing? Do you know of the ease with doing a conversion? Are you able to have complete control in what you want to render in the components/widgets (for example, I can sublcass a JPanel and over ride the paint method to do any type of drawing I want. Can you do this in TWL?).

davedes wrote:
This sounds like a bug. First try updating your libraries (lwjgl.jar and the native libraries) to the latest version of LWJGL. If it persists you can post a small test-case in the BUG/RFE forum.


Ok, thanks - I'll do this and see if it goes away. If not, I'll post a report.

davedes wrote:
If you plan on rendering that many images, there are lots of optimizations discussed in this thread:
viewtopic.php?p=26686#p26686 (I am getting 17,000+ images at 60 FPS)


Cool! I'll check this out thank you.

davedes wrote:
Don't get too caught up thinking about "how it would be done in Java2D," as that line of thought will only slow you down. e.g. Double-buffering your entire scene is unnecessary.


I have been using Java2D since around 2006 and I have never been pleased with the performance. I didn't have the skills back then(was in college) to do anything else but today, it is a much different story. I am quite happy with how easy it is to do the same things in Slick2D. Sure, I need to learn more about how it works and change the way I think and do things, but it certainty is a lot better based on my experimentation and reading these forums.

davedes wrote:
You should always be distributing the LWJGL native libraries along with your program. There are a lot of tools that make it useful to package an LWJGL application into a single JAR/executable.

If OpenGL is not supported on the computer, then your game will simply not run. This is rather unlikely, though; most people looking to play computer games will have a graphics card that supports OpenGL at some level. Unless you are targeting ancient systems, you shouldn't worry too much about this. If compatibility is your top concern, either abstract your rendering (a lot of work for very little gain) or stick purely to Java2D. But, realistically, the number of users with no OpenGL support is minuscule.


Thanks. I read something, which I didn't truly believe, that said only 70% of all machines had OpenGL. So I'm glad that it was indeed wrong.


Top
 Profile  
 
PostPosted: Thu Mar 15, 2012 6:10 pm 
Offline
Slick Zombie

Joined: Sat Jan 27, 2007 7:10 pm
Posts: 1469
Quote:
Can you talk a little more about how Swing and OpenGL do not behave well with each other? What sort of problems happen and so on? Are there any showstoppers? In my proof of concept, I haven't really seen anything except that one instance of crashing (which as you make mention of, is probably due to out dated libs). But it is just a simple application and uses a couple components/JInternalFrames.

It should work for most applications, though you will have a smoother, more efficient and much easier time using pure LWJGL.

The main problems I can see with Swing/OpenGL mix: threading, heavyweight vs. lightweight components, problems in applets (currently doesn't work with Mac), performance loss, memory footprint, etc. It simply adds a lot of complexity and overhead to what should be a simple solution: using pure OpenGL to render the components and handle input. And, most importantly, your game's GUI will look like crap because it won't be themed. ;)

Quote:
I need a GUI that has a lot of features and has flexibility. I see people refer to TWL (I think it is called). This is probably best asked the developers, but maybe someone knows and can chim in: Is the feature set for this comparable to Swing? Do you know of the ease with doing a conversion? Are you able to have complete control in what you want to render in the components/widgets (for example, I can sublcass a JPanel and over ride the paint method to do any type of drawing I want. Can you do this in TWL?).

Yes, TWL is extremely flexible and supports all the major widgets you would want. It will be difficult to migrate from Swing to TWL since the two are very different, and many beginners may find it frustrating/complicated to learn.

IMHO if you want your game to be as smooth as possible, you should scrap Java2D/Swing completely and focus your efforts on learning Swing and TWL (or another library like Nifty-GUI). That way you will not have any unnecessary junk/overhead going on in your game, you will learn to code in Slick/LWJGL using better practices, and you will open up the possibility of using OpenGL features to enhance your game (for example, shaders).

Just my two cents. :)


Top
 Profile  
 
PostPosted: Fri Mar 16, 2012 3:12 pm 
Offline
Oldbie

Joined: Thu Mar 15, 2012 12:38 am
Posts: 261
davedes wrote:
It should work for most applications, though you will have a smoother, more efficient and much easier time using pure LWJGL.

The main problems I can see with Swing/OpenGL mix: threading, heavyweight vs. lightweight components, problems in applets (currently doesn't work with Mac), performance loss, memory footprint, etc. It simply adds a lot of complexity and overhead to what should be a simple solution: using pure OpenGL to render the components and handle input. And, most importantly, your game's GUI will look like crap because it won't be themed. ;)


Thanks, I shall check out TWL and maybe it won't be as bad as I think it will be.


davedes wrote:
Yes, TWL is extremely flexible and supports all the major widgets you would want. It will be difficult to migrate from Swing to TWL since the two are very different, and many beginners may find it frustrating/complicated to learn.


This is good then. I have a lot of custom components in many different types of open windows were I do special rendering in my game.

davedes wrote:
IMHO if you want your game to be as smooth as possible, you should scrap Java2D/Swing completely and focus your efforts on learning Swing and TWL (or another library like Nifty-GUI). That way you will not have any unnecessary junk/overhead going on in your game, you will learn to code in Slick/LWJGL using better practices, and you will open up the possibility of using OpenGL features to enhance your game (for example, shaders).

Just my two cents. :)


I assume you made a typo when you said "focus your efforts on learning Swing and TWL", you mean Slick and TWL?


I did have another question...this deals with drawing big images. Right now, I have a big (2000x2000) background image, and I have read on these forums that I shouldn't draw images bigger than 512x512 and instead, draw them in pieces. I assume it is OK to hold the image in memory at it's big size, but when I perform the draw, I simply draw it in 512x512 chunks? I think this is OK because I've read people saying to make a texture of everything in their game and then draw pieces from that.

Also, this is somewhat related, but my previous "benchmark" where I was able to draw many transparent, scaled and rotated images...I was drawing wrong sized images (300x300). I changed these images to 256x256 and was able to draw 4,800 instead of 2,700. :D


Top
 Profile  
 
PostPosted: Fri Mar 16, 2012 7:16 pm 
Offline
Slick Zombie

Joined: Sat Jan 27, 2007 7:10 pm
Posts: 1469
Quote:
I assume you made a typo when you said "focus your efforts on learning Swing and TWL", you mean Slick and TWL?


I did have another question...this deals with drawing big images. Right now, I have a big (2000x2000) background image, and I have read on these forums that I shouldn't draw images bigger than 512x512 and instead, draw them in pieces. I assume it is OK to hold the image in memory at it's big size, but when I perform the draw, I simply draw it in 512x512 chunks? I think this is OK because I've read people saying to make a texture of everything in their game and then draw pieces from that.

Also, this is somewhat related, but my previous "benchmark" where I was able to draw many transparent, scaled and rotated images...I was drawing wrong sized images (300x300). I changed these images to 256x256 and was able to draw 4,800 instead of 2,700.

Yep, typo.

Most modern cards will support textures up to 2048x2048. A size of 512x512 is suggested for maximum compatibility, but if you know you'll be targeting some more modern systems, then you can bump up to 1024x1024 or 2048x2048.

Stitching images together is not difficult; it simply means rendering the images side-by-side. You might find some difficulty if you plan to scale the images (i.e. the borders between the stitched images might become apparent), but there are ways around that, too. If your image is held in a large PNG file already, you can use Slick's BigImage utility to load and render the image using this technique.

Though usually this should be used as a last resort -- why do you need such large background images? Are there parts of the background you can repeat or stretch to save memory and reduce fill rate? etc. Most often there are better solutions.

As for your benchmark; Slick converts all images to a power-of-two size; so your 300x300 images are actually being stored as 512x512 textures (causing the decrease in performance). This is for maximum compatibility with OpenGL (some drivers only support power-of-two texture sizes) and performance (even new drivers are generally more efficient with power-of-two sizes). It's a good idea to make all of your images power of two sizes (32, 64, 128, 256, 512, 1024, etc).

Future revisions to Slick will include non-power-of-two textures (if supported by the driver) for special situations. :) I also plan to fix up the Wiki with more information about this, since most people aren't aware of it.

P.S. Make sure you're using Image.startUse / drawEmbedded / endUse for maximum performance!


Top
 Profile  
 
PostPosted: Fri Mar 16, 2012 7:35 pm 
Offline
Oldbie

Joined: Thu Mar 15, 2012 12:38 am
Posts: 261
Before I reply to your responses, I just want to say - thank you very much for taking the time to reply to my posts in the thread. It seems this forum has a lot of activity and you sure do answer a lot of questions. It has been very helpful.

davedes wrote:
Most modern cards will support textures up to 2048x2048. A size of 512x512 is suggested for maximum compatibility, but if you know you'll be targeting some more modern systems, then you can bump up to 1024x1024 or 2048x2048.


What happens if an older card tries to use a game that has a big texture? Would it just not work or suffer performance degradation by converting the big image into something it can handle?

I would like to have the game work on as many machines as possible.

davedes wrote:
Stitching images together is not difficult; it simply means rendering the images side-by-side. You might find some difficulty if you plan to scale the images (i.e. the borders between the stitched images might become apparent), but there are ways around that, too. If your image is held in a large PNG file already, you can use Slick's BigImage utility to load and render the image using this technique.


This background image won't be scaled - so I can break it up. I tried to write some code that used startUse/drawEmbedded/endUse and drew portions from the large image. I didn't notice a performance change so maybe I wasn't doing it correctly.

Should I load the big image into memory, and then perform some operations to reduce it into many smaller images (ie: place them in an array) and then during render loop and startUse/etc? While I understand the process conceptually, my ignorance :oops: :oops: is showing here with Slick and the right way to do it for maximum performance.

davedes wrote:
Though usually this should be used as a last resort -- why do you need such large background images? Are there parts of the background you can repeat or stretch to save memory and reduce fill rate? etc. Most often there are better solutions.


Truthfully, this sized image is just a temporary one. I wanted to have it scaled (once) during game load to fit the player's desired resolution. It is an image of a space background. It never changes - it is never translated, rotated or scaled. And for all intents and purposes, will forever be static.

davedes wrote:
As for your benchmark; Slick converts all images to a power-of-two size; so your 300x300 images are actually being stored as 512x512 textures (causing the decrease in performance). This is for maximum compatibility with OpenGL (some drivers only support power-of-two texture sizes) and performance (even new drivers are generally more efficient with power-of-two sizes). It's a good idea to make all of your images power of two sizes (32, 64, 128, 256, 512, 1024, etc).


That explains it nicely and makes sense with my huge performance increase. I was very pleased :) I am going to be going through and modifying my images.

davedes wrote:
P.S. Make sure you're using Image.startUse / drawEmbedded / endUse for maximum performance!


I tried this on my program and after a certain scale level. If the scale level is under 0.6, it doesn't rotate, only translates. I did a startUse and endUse before my loop to render the images with drawEmbedded. :shock: :shock: :shock: I was able to draw 25,000 of these images and stay at 200 FPS! I could probably render another 5,000 before I hit 60 FPS.



I'm trying to run this test program on my wife's laptop (It is pretty decent, has 6 GB of RAM, for example. I don't know the expect CPU or video card yet) and trying to get some results there.


Top
 Profile  
 
PostPosted: Fri Mar 16, 2012 7:55 pm 
Offline
Slick Zombie

Joined: Sat Jan 27, 2007 7:10 pm
Posts: 1469
Quote:
What happens if an older card tries to use a game that has a big texture? Would it just not work or suffer performance degradation by converting the big image into something it can handle?

I would like to have the game work on as many machines as possible.

Slick simply throws an error if you try creating an Image that's too big for the user's driver. You can check the max texture size before loading with BigImage.getMaxSingleImageSize() -- call this from the GL thread (i.e. in your game's init method). From there, you could either shrink the textures to the proper size (first decode the textures, then perhaps use some Java2D trickery to resample the image) or you could implement a system that picks a separate set of resources (i.e. smaller textures).

Each of these would require a bit of work (the latter would increase your game's distribution size dramatically) -- the easiest way to ensure maximum compatibility would be to make all of your textures no bigger than 512x512, or simply distribute your game with a "low-res" version.

StartUse/endUse will almost always be more performant than repeated calls to Image.draw; however the difference will be negligible if you're only rendering a few images. You would use it in a loop like so:
Code:
sheet.startUse();
for (. . .)
    sheet.drawEmbedded(. . .);
sheet.endUse();


StartUse/endUse is particularly useful for things like texture atlases. Every time OpenGL renders a new image, it needs to "bind" the internal texture. Binding textures can be relatively expensive if you're doing it many times, so people tend to use things like texture atlases (putting sprites, glyphs, UI elements, etc. in the same large texture) and other tricks to reduce texture binds. Multiple Image objects can share the same backing Texture -- methods like getSubImage and getScaledCopy return "shallow copies" that will point to the same Texture.

Quote:
Truthfully, this sized image is just a temporary one. I wanted to have it scaled (once) during game load to fit the player's desired resolution. It is an image of a space background. It never changes - it is never translated, rotated or scaled. And for all intents and purposes, will forever be static.

Scaling images is very quick with OpenGL, you don't need to worry about "scaling it once" like in Java2D.

How I implemented my space background: draw a 256x256 gradient image scaled to the user's resolution, then rendered star sprites with some randomization across the screen (three sizes; small, medium, big). All of these images are contained in the same texture atlas, which fits well under 512x512 (and still has space for other textures). Of course, this might not be the look you're going for, but it gives you an idea of a different way to approach the situation:
viewtopic.php?p=26724#p26724

Quote:
If the scale level is under 0.6, it doesn't rotate, only translates.

Are you using the code from Slick's development branch? The rotation parameter for drawEmbedded is pretty new, I'll look into why it might not be working.


Top
 Profile  
 
PostPosted: Fri Mar 16, 2012 8:01 pm 
Offline
Oldbie

Joined: Thu Mar 15, 2012 12:38 am
Posts: 261
davedes wrote:
Quote:
If the scale level is under 0.6, it doesn't rotate, only translates.

Are you using the code from Slick's development branch? The rotation parameter for drawEmbedded is pretty new, I'll look into why it might not be working.


Oh, boy - I made a bad explanation here.

I mean - I have written code to make it not rotate - it rotates just fine when I want it to rotate :)

My bad on the confusion.

I wrote the code not to rotate over a certain scale because it was so far away, you couldn't really see it too much so figure, might as well as not have it rotate at that small of a view.


Top
 Profile  
 
PostPosted: Fri Mar 16, 2012 8:04 pm 
Offline
Slick Zombie

Joined: Sat Jan 27, 2007 7:10 pm
Posts: 1469
Fair nuff.

Be sure to use the dev branch for "bleeding-edge" features and optimizations. :)


Top
 Profile  
 
PostPosted: Fri Mar 16, 2012 8:24 pm 
Offline
Oldbie

Joined: Thu Mar 15, 2012 12:38 am
Posts: 261
davedes wrote:
Slick simply throws an error if you try creating an Image that's too big for the user's driver. You can check the max texture size before loading with BigImage.getMaxSingleImageSize() -- call this from the GL thread (i.e. in your game's init method). From there, you could either shrink the textures to the proper size (first decode the textures, then perhaps use some Java2D trickery to resample the image) or you could implement a system that picks a separate set of resources (i.e. smaller textures).

Each of these would require a bit of work (the latter would increase your game's distribution size dramatically) -- the easiest way to ensure maximum compatibility would be to make all of your textures no bigger than 512x512, or simply distribute your game with a "low-res" version.

StartUse/endUse will almost always be more performant than repeated calls to Image.draw; however the difference will be negligible if you're only rendering a few images. You would use it in a loop like so:
Code:
sheet.startUse();
for (. . .)
    sheet.drawEmbedded(. . .);
sheet.endUse();


Ok, this was very useful information to know. Thank you.

davedes wrote:
StartUse/endUse is particularly useful for things like texture atlases. Every time OpenGL renders a new image, it needs to "bind" the internal texture. Binding textures can be relatively expensive if you're doing it many times, so people tend to use things like texture atlases (putting sprites, glyphs, UI elements, etc. in the same large texture) and other tricks to reduce texture binds. Multiple Image objects can share the same backing Texture -- methods like getSubImage and getScaledCopy return "shallow copies" that will point to the same Texture.


Is getSubImage a fast call?


davedes wrote:
Scaling images is very quick with OpenGL, you don't need to worry about "scaling it once" like in Java2D.


This is excellent news - I have zooming capability in many different portions of my game so there is a lot of scaling going on.

davedes wrote:
How I implemented my space background: draw a 256x256 gradient image scaled to the user's resolution, then rendered star sprites with some randomization across the screen (three sizes; small, medium, big). All of these images are contained in the same texture atlas, which fits well under 512x512 (and still has space for other textures). Of course, this might not be the look you're going for, but it gives you an idea of a different way to approach the situation:
viewtopic.php?p=26724#p26724


This is a pretty cool demo program.

I haven't done any research whatsoever on shaders, but would they be more efficient then doing a large amount of rotations? Depends on what I'm using?


So I took my test program and put it on my wife's laptop. The performance is bad. It has 6 GB of RAM with an ATI Mobility Radeon HD 4250 and a Phenom II N870 Triple Core. I can only get 60 FPS with 250 images rendering (compared to almost 5,000 on my machine), and at 500 images, I get around 35 FPS. I did some brief research on this card and people seem to think it is *decent*, enough to play games like Crysis and Minecraft on low settings.

Here is a page that I got from a quick google search: http://answers.yahoo.com/question/index ... 727AASDhwk
I found some benchmarks and the card is on the lower end: http://www.videocardbenchmark.net/mid_range_gpus.html (can do a search for Mobility Radeon HD 4250).

Based on this, I suspect the issue is mostly the card's? (hard to tell without looking at my code) On my main machine, I have a HD 6950 and I feel the performance I am getting with the test program is very acceptable. I can get 110 FPS in SW:TOR on it. IIRC my wife can get around 15-30 FPS on Everquest2 on her laptop. but that game is mostly a CPU bound instead of GPU.

Edit: Actually...I don't think she has ever updated her drivers! They are at least 15 months old and possibly even older.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 11 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group