Java-Gaming.org Hi !
Featured games (84)
games approved by the League of Dukes
Games in Showcase (555)
Games in Android Showcase (149)
games submitted by our members
Games in WIP (601)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
   Home   Help   Search   Login   Register   
  Show Posts
Pages: [1] 2 3 ... 92
1  Discussions / Miscellaneous Topics / Re: What I did today on: 2015-03-01 22:00:51
In addition to a lot of bug fixing on WSW, I did some compute shader experiments.

Basically I developed a generic compute shader for "simulating" some super basic heat conductivity. No actual graphics for it yet though. The idea is to supply the compute shader with a massive list of tiles. Each tile has a heat value (temperature/energy/whatever), the tile indices of its 4 neighbors and the conductivity coefficients to those neighbors. Heat spreading between tiles is simulated using something similar to a gaussian blur, with the weights modified by conductivity. My current performance tests look promising. At 60 updates per second I can update 64 000 000 tiles. Another way to put it is 3 841 707 432 tiles per second. Now, I wonder what it'll be useful for... =3
2  Discussions / Miscellaneous Topics / Re: What I did today on: 2015-03-01 13:14:08
Did some basic 2D bloom. I think it brightens the game up quite a bit. Pun intended.

With:
http://puu.sh/ghvFx/2720d0c38f.jpg
Without:
http://puu.sh/ghvGO/94e37f6095.jpg
I don't think that's how bloom works. I can't really see the difference except the brightness.
3  Java Game APIs & Engines / Engines, Libraries and Tools / Re: Java OpenGL Math Library (JOML) on: 2015-02-27 17:52:19
For tools...sure.  There are existing libraries.  Runtime:  You don't need doubles.

(EDIT: the extra precision is more often the interesting part...not the wider range)
By large range of values, I meant that you might have relatively large values (= a wider range of values) but you still need high precision, so we're basically saying the same thing.
4  Java Game APIs & Engines / Engines, Libraries and Tools / Re: Java OpenGL Math Library (JOML) on: 2015-02-27 14:53:17
Doubles are for serious computing.
Or, you know, a large range of values.
5  Discussions / Miscellaneous Topics / Re: What I did today on: 2015-02-26 11:09:58
That would work too, of course.
6  Discussions / Miscellaneous Topics / Re: What I did today on: 2015-02-26 08:11:22
TL;DR:
 - Don't assume anything about the ordering of vertex attributes! Nvidia assigns them in the order they're defined, but AMD does not!
 - Make sure you set vertex attribute and uniform locations BEFORE linking the shader! This is a silent "error" as OpenGL expects you to relink the shaders to apply those settings.
 - If your shader only outputs a vec3 instead of all 4 color channels, Nvidia and AMD behave differently. Nvidia outputs 0.0 for the alpha value while AMD outputs 1.0.

Fixed two critical bugs that only affected AMD GPUs!

The first one was a bug in my particle rendering code, causing the particles to appear in random color (usually bright magenta) and have random size. The problem was the vertex attributes, but this problem was only visible on AMD GPUs. In essence I set the vertex attribute locations AFTER linking the shader, so they weren't being applied. AMD's GLSL compiler simply assigned different vertex attribute locations so it read the particle data incorrectly.

Secondly, another piece of useful information for the future! My SRAA was not working correctly on AMD card, and I tracked down the problem to my lighting code. In essence, SRAA works by rendering the scene twice. In the first pass the scene is rendered and lit as normal, but we also store a semi-unique triangle ID for each pixel. In the second pass, we render ONLY triangle IDs to a multisampled texture. In a resolve pass, the non-multisampled lighting result is "upsampled" to MSAA quality by matching triangle IDs. In practice, it's a lot more complicated as I use both the current and previous frames in the upsampling, but that's the basic idea.

Anyway, the point here was how the I stored the triangle ID in the first pass. I render and light the scene to a GL_RGBA16F render target, with accumulated light intensity stored in RGB and triangle ID stored in A, the alpha channel. During lighting, the shader only outputs an RGB color:
1  
out vec3 fragColor;

In this case, Nvidia's shader compiler set alpha to 0.0, and since I was using additive blending this left the triangle ID unmodified. AMD's compiler however set alpha to 1.0, which corrupted the triangle IDs completely, leaving my SRAA resolve shader utterly confused.
7  Discussions / Miscellaneous Topics / Re: What I did today on: 2015-02-24 05:12:03
Implemented AOIT to compare it to my own OIT algorithm... Too tired for screenshots though. x___x
8  Java Game APIs & Engines / Engines, Libraries and Tools / LWJGL problem with shared contexts on: 2015-02-22 01:32:17
Due to some reasons we recently put our asset loading code in their own threads. These threads have their own shared OpenGL contexts, but it seems like we don't need it, so we'll probably make them normal threads. Anyway, the point is that sometimes when a shared context is made current on the new thread, it crashes due to some kind of concurrency issue sometimes. This is with the latest stable version of LWJGL 2. The problem seems related to capabilities checking that the context switch triggers.

Quote
java.lang.IllegalStateException: Function is not supported
   at org.lwjgl.BufferChecks.checkFunctionAddress(BufferChecks.java:58)
   at org.lwjgl.opengl.GL11.glGetError(GL11.java:1301)
   at org.lwjgl.opengl.Util.checkGLError(Util.java:57)
   at org.lwjgl.opengl.GLContext.getSupportedExtensions(GLContext.java:280)
   at org.lwjgl.opengl.ContextCapabilities.initAllStubs(ContextCapabilities.java:5802)
   at org.lwjgl.opengl.ContextCapabilities.<init>(ContextCapabilities.java:6240)
   at org.lwjgl.opengl.GLContext.useContext(GLContext.java:374)
   at org.lwjgl.opengl.ContextGL.makeCurrent(ContextGL.java:195)
   at org.lwjgl.opengl.DrawableGL.makeCurrent(DrawableGL.java:110)
   at org.lwjgl.opengl.SharedDrawable.makeCurrent(SharedDrawable.java:47)
   at engine.gl.GLThread.run(GLThread.java:60)

Quote
org.lwjgl.LWJGLException: GL11 not supported
   at org.lwjgl.opengl.ContextCapabilities.initAllStubs(ContextCapabilities.java:5806)
   at org.lwjgl.opengl.ContextCapabilities.<init>(ContextCapabilities.java:6240)
   at org.lwjgl.opengl.GLContext.useContext(GLContext.java:374)
   at org.lwjgl.opengl.ContextGL.makeCurrent(ContextGL.java:195)
   at org.lwjgl.opengl.DrawableGL.makeCurrent(DrawableGL.java:110)
   at org.lwjgl.opengl.SharedDrawable.makeCurrent(SharedDrawable.java:47)
   at engine.gl.GLThread.run(GLThread.java:60)

Creating a simple test program to reproduce the problem failed as usual...
9  Discussions / Miscellaneous Topics / Re: What I did today on: 2015-02-17 19:24:31
I found some nice optimizations for bounds testing in shaders. Basically my motion blur was blurring over the edge of the screen, resulting in either black color if I used texelFetch() which gave a dark aura around the screen when in motion, or being clamped to the edge pixel if I used texture() which inflated the weights of the edge pixels. Neither of these looked very good, so I decided to simply remove the samples that fell outside the screen. However, detecting these scenarios was expensive.
...

I have seen this kind of pattern multiple times. Its hlsl but maps well to hardware.
1  
2  
if (dot(1.0, saturate(texCoords) - texCoords) != 0.0)
  samples += 1.0;


I tried a similar version too:
1  
samples += float(texCoords == clamp(texCoords, vec2(0), resolution));


8.30 pixels per second.

EDIT: If your coordinates are normalized and you use clamp(<value>, 0.0, 1.0), the clamp becomes free, in which case this one is ALMOST as fast as the float multiplied version (version 4).

EDIT2: Turns out that clamp DIDN'T become free. Since the texture coordinate varying input is never modified, there is no previous instruction that clamp can piggyback on, so it needs to add a MOV instruction with CLAMP to get the same result. It's faster than a MIN and a MAX, but still slower than the optimized float multiplied thingy.
10  Discussions / Miscellaneous Topics / Re: What I did today on: 2015-02-17 15:49:19
I found some nice optimizations for bounds testing in shaders. Basically my motion blur was blurring over the edge of the screen, resulting in either black color if I used texelFetch() which gave a dark aura around the screen when in motion, or being clamped to the edge pixel if I used texture() which inflated the weights of the edge pixels. Neither of these looked very good, so I decided to simply remove the samples that fell outside the screen. However, detecting these scenarios was expensive.

Simply using an if-statement to test whether the coordinates are inside the screen was dead slow as this was compiled to 1 branch per sample.
1  
2  
3  
if(texCoords.x >= 0 && texCoords.y >= 0 && texCoords.x < resolution.x && texCoords.y < resolution.y){
   samples += 1.0;
}


However, there's a trick you can use. By casting the resulting boolean to a float, we can convert the boolean to 1.0 if it's true and 0.0 if it's false! Exactly what we want!
1  
      samples += float(texCoords.x >= 0 && texCoords.y >= 0 && texCoords.x < resolution.x && texCoords.y < resolution.y);

Sadly, this still compiles to the same thing as the if-statement. That's weird since I know that GPUs have specific instructions to set the value of a register based on a simple comparison. As an example, this line:
1  
float x = float(someValue < 1);

compiles to this instruction:
1  
x: SETGT       R0.x,  1.0f,  PV0.x  

It would seem that the boolean &&-operators are messing this up, causing it to revert to branches. Let's try casting the result of each comparison to floats and then use a simple float multiply between them to effectively and them together.
1  
samples += float(texCoords.x >= 0)*float(texCoords.y >= 0)*float(texCoords.x < resolution.x)*float(texCoords.y < resolution.y);

Bam! The comparison compiles to 2 SETGE (greater-equals) instructions, 2 SETGT (greater-than) instructions and 3 multiplies. I need to do this 16 times per pixel, once for each sample, so this saves a load of work! There is one final optimization we can make to improve this code on AMD's vector GPUs. AMD's older GPUs are a bit funny in that they run each shader on 4 or 5 different cores at the same time, trying to do as much as possible at the same time. This code:
1  
float x = a + b + c + d;

would fit this extremely badly. GLSL requires the GPUs to enforce the order of the operations, so none of these instructions can be run in parallel. First we do a+b, then (a+b)+c, then finally ((a+b)+c)+d, which requires 3 cycles. If we add some "unnecessary" parenthesises, we can encourage vector GPUs to do these additions in parallel without affecting the performance of scalar GPUs that don't have this problem:
1  
float x = (a + b) + (c + d)

This only takes 2 cycles, as a+b and c+d can both be calculated in the first cycle, and then (a+b)+(c+d) can be calculated in the second cycle, making this chain of addition 50% faster. Doing this for the bounds testing gives this code:
1  
samples += (float(texCoords.x >= 0)*float(texCoords.y >= 0))*(float(texCoords.x < resolution.x)*float(texCoords.y < resolution.y));


Theoretical performance of the 4 versions of bounds checking done for 16 samples on a Radeon HD 6870:
1. 2.04 pixels per second
2. 2.02 pixels per second
3. 11.20 pixels per second
4. 11.79 pixels per second

All in all, that's a 5.78x improvement compared to a naive if-statement.
11  Game Development / Newbie & Debugging Questions / Re: Byte buffer crashing on: 2015-02-11 12:11:30
Checking for errors using glGetError() can ruin performance...
Sorry for saying that, but saying this right after spasi pointed out to check for a GL error, is just stupid.

As long as there is evidence of your code having a programming error in it, you should not give a damn about performance at all, but instead should focus on getting rid of the source of the error. Therefore you most certainly want to insert an error check after each and every single GL call...

If I wanted my program to crash without a decent stack trace in a way that's hard to debug I wouldn't use Java in the first place. I can check for null every time I map a buffer, but having to call glGetError() if debug callbacks aren't supported is a nightmare.
12  Game Development / Newbie & Debugging Questions / Re: Byte buffer crashing on: 2015-02-11 11:45:04
Checking for errors using glGetError() can ruin performance since it causes a driver thread sync. Since there is no way (?) to figure out the address of the returned buffer, I'd strongly prefer it returning null. That makes it much clearer what's going on.
13  Discussions / Miscellaneous Topics / Re: What I did today on: 2015-02-10 03:15:56
Rewrote my texture streaming to accommodate a few new things based on my findings in this thread: http://www.java-gaming.org/topics/files-on-your-harddrive-can-be-mapped-and-passed-directly-into-textures-buffers/35504/msg/336278/view.html#msg336278. I ended up not using mapped files, but I did rewrite it to use FileChannels instead of InputStreams, which should give a decent performance boost anyway. The biggest change lies in how textures can be sourced now. Before the streamer simply looked in a directory you specified, which got awfully messy once you had 50 textures or so. Now it's possible to put the textures multiple folders and also to pack together textures into a single massive file, and feed the streamer a TextureSource object for that folder/file, and tadaa, the streamer can magically find them!
14  Game Development / Shared Code / Re: Files on your harddrive can be mapped and passed directly into textures/buffers on: 2015-02-04 00:55:52
Riven, you mentioned writing my own file mapping implementation using JNI. That sounds a bit annoying. Isn't there any simple implementation of it out there that I can use?
15  Game Development / Shared Code / Re: Files on your harddrive can be mapped and passed directly into textures/buffers on: 2015-02-03 20:29:39
I'll look into this more and update the first post to reflect this discussion once I've gotten rid of the 25cm of snow that fell outside our house...
16  Game Development / Shared Code / Re: Files on your harddrive can be mapped and passed directly into textures/buffers on: 2015-02-03 19:48:22
Is there a serious risk of a out-of-memory crash unless I do that?
17  Game Development / Shared Code / Re: Files on your harddrive can be mapped and passed directly into textures/buffers on: 2015-02-03 19:22:06
Ah, right. =3 No problem.
18  Game Development / Shared Code / Re: Files on your harddrive can be mapped and passed directly into textures/buffers on: 2015-02-03 19:00:12
But when are you passing textures between processes? Threads maybe, but not processes.

Although I have seen some talk about spinning up multiple jvms in this fashion just so that each jit can focus on a core area of the code, etc. and be profitable if the IPC has low-enough overhead (mmap'ed files). Probably only useful in very particular circumstances. Maybe texture decompression from a conventional format would be in this category.
I'm not following. I just want to read raw data from the harddrive and pass it to OpenGL in the most efficient way possible. Since mapping the file seems like not only the fastest but also the simplest and most memory efficient way, why is it not fit for streaming? I don't have multiple processes.
19  Game Development / Shared Code / Re: Files on your harddrive can be mapped and passed directly into textures/buffers on: 2015-02-03 18:12:10
Probably not applicable to texture streaming (unless you're evil)...
Why not? My streamable textures are just each mipmap's compressed texture data dumped to a big file. Being able to map a whole mipmap and pass it in sounds immensely useful, and decompressing the texture is simply too slow, especially since S3TC and BPTC textures don't compress very well.
20  Game Development / Shared Code / Files on your harddrive can be mapped and passed directly into textures/buffers on: 2015-02-03 17:38:16
EDIT:
Don't do this! It has some severe problems which can cause your program to grind into a halt after mapping a lot of files. See the below discussion for more details! I recommend using FileChannels with a temporary direct buffer, not to map them!

Hello.

Today I tried out something interesting. I was working on making it possible to source my streamable textures from multiple sources (asset files, texture packs and raw files in a directory) when I stumbled upon an interesting function. FileChannels allow you to get a MappedByteBuffer for a certain range of a file on your harddrive. This byte buffer is direct, meaning that it's possible to pass it directly into OpenGL functions like glTexImage2D() and glBufferData(). For texture data, this can be pretty worthless. Most of the time the data is stored in some kind of image format (PNG or even JPG) which needs to be decompressed before it can be passed into glTexImage2D(), but for my streaming system this decompression was way too slow. My streamable texture files contain the raw image data compressed using S3TC or BPTC which I simply dump into glCompressedTexImage2D(). To test out the potential gains of using mapped files, I've developed a small test program which compares the CPU performance of 3 different ways of loading raw texture data from a file.

The first way of loading stuff is with old-school input streams. This requires a lot of copies, since FileInputStreams work with byte[]s, not ByteBuffers. We have to read the texture data into a byte[], copy it to a direct ByteBuffer and then pass it to glTexImage2D().
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
   private static byte[] bytes = new byte[DATA_LENGTH];
   private static ByteBuffer buffer = BufferUtils.createByteBuffer(DATA_LENGTH);

   private static long loadStream() throws Exception {
      long startTime = System.nanoTime();

      FileInputStream fis = new FileInputStream(RAW_FILE);
     
      int read = 0;
      while(read < DATA_LENGTH){
         int r = fis.read(bytes, read, DATA_LENGTH-read);
         if(r == -1){
            throw new IOException();
         }
         read += r;
      }
      buffer.put(bytes).flip();
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
     
      fis.close();
     
      return System.nanoTime() - startTime;
   }


The second way is to use NIO and FileChannels. FileChannels work on ByteBuffers directly, so we can use a direct ByteBuffer from the start!
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
   private static ByteBuffer buffer = BufferUtils.createByteBuffer(DATA_LENGTH);

   private static long loadChannel() throws Exception{
      long startTime = System.nanoTime();

      FileInputStream fis = new FileInputStream(RAW_FILE);
      FileChannel fc = fis.getChannel();
     
      while(buffer.hasRemaining()){
         fc.read(buffer);
      }
      buffer.flip();
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
     
      fc.close();
      fis.close();
     
      return System.nanoTime() - startTime;
   }


The last most awesome way is to use NIO and FileChannels to map part of the file as a MappedByteBuffer. This is just so magical and simple.
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
   private static long loadMapped() throws Exception {
      long startTime = System.nanoTime();

      FileInputStream fis = new FileInputStream(RAW_FILE);
      FileChannel fc = fis.getChannel();
     
      MappedByteBuffer mbb = fc.map(MapMode.READ_ONLY, 0, fc.size());
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, mbb);
     
      fc.close();
      fis.close();

      return System.nanoTime() - startTime;
   }


As you can see, there's some timing code in each function. The average time taken to do these operations over a few thousand runs in a loop (all reading the same file over and over again, so it's not representative for IO performance, only CPU performance) is listed below:

Stream:  0.657057 ms
Channel: 0.207856 ms
Mapped:  0.169004 ms

So it's not only simple as hell to do, it's faster as well and doesn't require any temporary memory!
21  Discussions / Miscellaneous Topics / Re: What I did today on: 2015-02-01 17:02:07
Gah. Which JDK was it?

Cas Smiley
An ancient Java 7 Update 45 one...
22  Discussions / Miscellaneous Topics / Re: What I did today on: 2015-02-01 05:43:54
Spent the whole night trying to figure out why the Xbox controller library was crashing the entire JVM. The weird part was that it was crashing in random places all the time. The most fun (not) crash was when it crashed on this function:
1  
2  
3  
   public int getID(){
      return id;
   }

Yep. After spending several hours thinking about what kind of insane concurrency issue that could cause this, we finally found a crash report which seemed to imply that it was a HotSpot JIT compiler bug. We updated our JDKs and the error seems to have disappeared... X___X
23  Discussions / Miscellaneous Topics / Re: What I did today on: 2015-01-31 13:58:30
Spent almost the whole day working on finding curves ...

You say dynamic curve fitting...I automatically think splines.  I think splines...I automatically think hermite.
No, I tested a number of points, figured out the basic shape of it, then simply used an algorithm which randomly modified the coefficients and tried to minimized the total squared error. For example, I had values which looked like the shape of a*x/exp(b*x), so I ran it through a program that found the correct values of a and b, and in the end I figured out that A was the alpha value of the pixel, and b required a second function to compute, and in the end I could simplify the whole expression to a much simpler function.
24  Discussions / Miscellaneous Topics / Re: What I did today on: 2015-01-31 04:51:00
Spent almost the whole day working on finding curves that describe different parameters for my order-independent transparency stuff. I even wrote a small program that could find correct coefficients to create a graph that passed through a set of points with minimal quadratic error. That numerical analysis class at my uni actually was helpful in the end! In the end I managed to solve everything but the result was a bit disappointing so it's probably not really useful in practice... Still, I managed to fix a few issues and I also understand the math behind it all a bit more now.
25  Game Development / Newbie & Debugging Questions / Re: Access violation with glDrawArrays on: 2015-01-29 15:09:45
Fixed...
26  Game Development / Newbie & Debugging Questions / Re: Access violation with glDrawArrays on: 2015-01-29 13:26:29
Performance is excellent. I used to simply draw each instance by itself and upload the skeleton data into a uniform matrix array. The random access of the texture buffer is at least as fast as uniform array access it seems, and in addition I can randomly access any amount of data in the shader which allows for instancing and more bones per instance. The maximum number of uniform variables quickly becomes a limit in my case, but with texture buffers I'm only limited by the precision of my bone ID vertex attribute, which is currently 1 byte. 256 bones per model ought to be enough for anyone (tm).
27  Discussions / General Discussions / Re: Schemes to teach the masses to code on: 2015-01-29 04:59:37
We have those cooking and household classes in Swedish high schools too.
28  Game Development / Newbie & Debugging Questions / Re: Access violation with glDrawArrays on: 2015-01-29 04:14:59
you prefer TBO's over SSBO's ?
Yes, since I'm limited to OGL3 I haven't looked into SSBOs very much.

For skinned models, I also batch up all the skeleton data into one MASSIVE VBO which is accessed as a texture buffer, which I can strongly recommend if you have data that is reused between vertices like skinning data.

Could you explain what data you refer when you say skeleton data?
Each skeleton animated instance needs a skeleton. Our player model has around 70 bones. Each of these need to be available in the vertex shader for random access (up to 4 per vertex), so I dump each bone into a TBO as a 4x3 matrix and can easily get whatever bone I need that way.
29  Game Development / Newbie & Debugging Questions / Re: Access violation with glDrawArrays on: 2015-01-28 20:42:11
I basically do the same as Cas. I try to batch up as much data as possible into as few VBOs as possible. I currently batch up all my model vertex data into a single VBO and all indices in a second VBO. For skinned models, I also batch up all the skeleton data into one MASSIVE VBO which is accessed as a texture buffer, which I can strongly recommend if you have data that is reused between vertices like skinning data.
30  Game Development / Newbie & Debugging Questions / Re: Experienced game developers opinions needed! on: 2015-01-28 18:03:33
Seriously though, you should consider getting a better name. >___<
Pages: [1] 2 3 ... 92
 
BurntPizza (37 views)
2015-02-27 06:09:35

BurntPizza (27 views)
2015-02-27 05:56:17

Riven (20 views)
2015-02-27 02:34:15

Riven (25 views)
2015-02-27 01:47:26

Riven (27 views)
2015-02-27 01:46:04

BurntPizza (21 views)
2015-02-27 00:52:04

BurntPizza (24 views)
2015-02-27 00:50:29

Riven (43 views)
2015-02-26 23:38:45

Riven (20 views)
2015-02-26 23:37:24

BurntPizza (37 views)
2015-02-26 21:13:04
How to: JGO Wiki
by Mac70
2015-02-17 20:56:16

2D Dynamic Lighting
by ThePixelPony
2015-01-01 20:25:42

How do I start Java Game Development?
by gouessej
2014-12-27 19:41:21

Resources for WIP games
by kpars
2014-12-18 10:26:14

Understanding relations between setOrigin, setScale and setPosition in libGdx
by mbabuskov
2014-10-09 22:35:00

Definite guide to supporting multiple device resolutions on Android (2014)
by mbabuskov
2014-10-02 22:36:02

List of Learning Resources
by Longor1996
2014-08-16 10:40:00

List of Learning Resources
by SilverTiger
2014-08-05 19:33:27
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!