Java-Gaming.org Hi !
Featured games (83)
games approved by the League of Dukes
Games in Showcase (527)
Games in Android Showcase (127)
games submitted by our members
Games in WIP (593)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1] 2
  ignore  |  Print  
  The Last Word in Text Rendering  (Read 12738 times)
0 Members and 1 Guest are viewing this topic.
Offline Ken Russell

JGO Coder




Java games rock!


« Posted 2007-01-04 18:26:43 »

Ladies and Gentlemen, I am pleased to announce the introduction of a Java 2D based TextRenderer class into the JOGL source tree, in the new package com.sun.opengl.util.j2d. This class provides high performance bitmapped text rendering, with complete Unicode support, into OpenGL drawables with an extremely small and simple API. Advanced string-by-string caching algorithms behind the scenes result in a minimum number of OpenGL pipeline state changes; for a given TextRenderer, all strings' cached rendering results are placed on a single OpenGL texture. A rectangle packing algorithm manages the placement of the strings on the backing store completely automatically; all details of the caching are hidden from the end user and are not exposed in the public API.

The renderer is extremely easy to use, and looks almost exactly like Java 2D's Graphics.drawString() API. Here's a Hello, World code snippet:

1  
2  
3  
4  
  TextRenderer renderer = new TextRenderer(new Font("SansSerif", Font.BOLD, 36));
  renderer.beginRendering(drawable.getWidth(), drawable.getHeight());
  renderer.draw("Hello, World!", 30, 30);
  renderer.endRendering();


There is a simple test of the new code in the jogl-demos workspace in demos.j2d.TestTextRenderer. More demos are coming.

Please take a look and give the new renderer a try and post with any feedback, comments or suggestions. The APIs in this area, including the related TextureRenderer and Overlay classes, are still very much open for consideration, so if you have suggestions about changes or improvements please post them. Also please let us know if you run into any problems, in particular any performance problems, with the new code; it's been debugged to a certain degree but it's certainly possible some issues remain, and we're continuing to look into some situations where performance is not as good as expected.
Offline oNyx

JGO Coder


Medals: 2


pixels! :x


« Reply #1 - Posted 2007-01-04 19:13:07 »

What would happen if the width of the String exceeds 4096 pixels (or whatever else maximum texture size is in effect)?

弾幕 ☆ @mahonnaiseblog
Offline Orangy Tang

JGO Kernel


Medals: 56
Projects: 11


Monkey for a head


« Reply #2 - Posted 2007-01-04 20:32:28 »

How do you deal with state changes? Do you save and restore all changed state (with the obvious performance problems) or do you provide a list of state which may be undefined after using the text renderer?

[ TriangularPixels.com - Play Growth Spurt, Rescue Squad and Snowman Village ] [ Rebirth - game resource library ]
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Riven
« League of Dukes »

« JGO Overlord »


Medals: 834
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #3 - Posted 2007-01-04 20:47:06 »

Ehm... what if you do something like this:

1  
2  
3  
4  
5  
6  
7  
8  
9  
TextRenderer r = new TextRenderer();

int counter = 0;
while(newFrame)
{
   ...
   r.draw("Current frame: "+(counter++));
   ...
}


Wouldn't that fill the texture after a few frames?


Can't you have a texture with used characters, and draw the Strings as a series of
quads with proper UVs (selecting the right character). It's not very likely that the user will
fill the texture with unique characters, as opposed to unique Strings.


If I misunderstood the internal workings of the TextRenderer, please elaborate Smiley

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline Ken Russell

JGO Coder




Java games rock!


« Reply #4 - Posted 2007-01-04 21:44:53 »

What would happen if the width of the String exceeds 4096 pixels (or whatever else maximum texture size is in effect)?

That would be a problem. Internally the renderer would probably fail to allocate its texture. Do you think this is likely to be a real issue? 4096 pixels wide is a pretty long string. We expect that applications are going to handle flowing of text within regions as necessary; this is not handled by this API.
Offline Ken Russell

JGO Coder




Java games rock!


« Reply #5 - Posted 2007-01-04 21:58:03 »

How do you deal with state changes? Do you save and restore all changed state (with the obvious performance problems) or do you provide a list of state which may be undefined after using the text renderer?

We push and pop a few attributes which are used by the renderer (GL_ENABLE_BIT, GL.GL_DEPTH_BUFFER_BIT, GL.GL_TRANSFORM_BIT) and push and pop the modelview and projection matrices. In theory these are all "downward" calls (no glGet operations) and are intended to be batched up so that multiple strings are rendered in between. In practice I've found that the run-time cost of this state pushing/popping is negligible. There are some performance anomalies of some of the underlying code which I'm continuing to look into, but I don't think the state management is an issue.
Offline Ken Russell

JGO Coder




Java games rock!


« Reply #6 - Posted 2007-01-04 22:08:58 »

Wouldn't that fill the texture after a few frames?

No. It's magic. Smiley If you look at the demos.j2d.TestTextRenderer class, it does exactly this (or something similar -- it updates the FPS counter every 100 frames or so), but the backing store remains constant size. The TextRenderer maintains a least recently used cache of strings and reuses the space on the backing store for strings which haven't been rendered recently, so the older "current frame" strings will be deleted soon.

There are certainly some policy issues to be worked through; your example might currently cause the backing store for the texture to expand to an unreasonably large size, since there isn't yet a throttle which forces out older strings faster as the pressure on the backing store gets higher. The goal is to make all of these cases work completely automatically and I think it's achievable.

Can't you have a texture with used characters, and draw the Strings as a series of
quads with proper UVs (selecting the right character). It's not very likely that the user will
fill the texture with unique characters, as opposed to unique Strings.

From my discussions with Phil Race from the Java 2D team, something like this is technically feasible, but also quite complicated. In order to provide complete Unicode support, it isn't possible to think of the problem in terms of characters (i.e., java "char"s). Instead, you have to think of it in terms of "glyphs", where a given rendered String will be decomposed into a series of glyphs. In the complete Unicode case, it isn't reasonable to ask the end user which glyphs to pre-render, so you have to cache them dynamically, and be very careful about composing them on-screen (handling things like bi-directional text, including having left-to-right and right-to-left text in the same String). For all of these reasons, we went down the route of String-by-String caching, and we'll see how far we get with this approach. If we need to switch to glyph-by-glyph caching, we can do this without changing the public API of the TextRenderer.
Offline oNyx

JGO Coder


Medals: 2


pixels! :x


« Reply #7 - Posted 2007-01-05 00:04:18 »

That would be a problem. Internally the renderer would probably fail to allocate its texture. Do you think this is likely to be a real issue? 4096 pixels wide is a pretty long string. We expect that applications are going to handle flowing of text within regions as necessary; this is not handled by this API.

Well, there are a bunch of cards with a 512x512 limit (and those voodoo cards with 256x256 max). If the game runs at 800x600 or something higher and you have a screen full of text... oops... big problem.

A simple semi solution would be to use scaling. Determine how big font would be, change the font size until it fits, scale that rect to the desired size. Well, at some point that will also fail and the text gets quickly unreadable etc.

You could split it up into fitting sizes, but you could reach a point where the whole LRU cache gets trashed completely (or even several times per frame).

Or well... dunno. That's the reason why I asked. Heh. Smiley

Hm.

Well, you could of course clip the thing (screen size enlarged to next max width/height border) before putting it into textures if it exceeds the texture width. Would be fiddly tho... It's sorta similar to drawing only the visible tiles of a tilemap and you only generate "fresh" tiles if necessary. Makes at least some sense, right? Smiley

弾幕 ☆ @mahonnaiseblog
Offline Ken Russell

JGO Coder




Java games rock!


« Reply #8 - Posted 2007-01-05 07:54:53 »

For the time being I think we'll stick with the current caching strategy and see how well it works in peoples' applications. I'm satisfied that the API doesn't need to change to support things like glyph-by-glyph caching instead of string-by-string caching, and the current caching strategy seems to be pretty fast at least when you hit in the cache frame to frame. We'll work through issues as they come in. Please give it a try if you have a chance.
Offline emzic

Senior Devvie





« Reply #9 - Posted 2007-01-05 11:49:05 »

first of all, thanks for this great feature.

i have replaced my current text render (all glyphs prerasterized on a 256x256 texture) with the new jogl TextRenderer and the font is much nicer and crispier but unfortunately i see a framerate drop by 20 fps.

could this be that the cache is running out of size and has to rasterize strings every frame?
my game uses a lot of text. (up to 2000 characters on screen every frame.)

www.embege.com - personal website
webstart blendinspect - OpenGL BlendingModes visualization.
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Ken Russell

JGO Coder




Java games rock!


« Reply #10 - Posted 2007-01-05 16:34:10 »

It's possible. We don't have a lot of logging code in the TextRenderer right now (we need more) but do you have a before-and-after test case? Could you either file a bug or email me offline (kbr at dev.java.net)?
Offline Ken Russell

JGO Coder




Java games rock!


« Reply #11 - Posted 2007-01-06 00:47:01 »

There was an incredibly stupid bug on my part at the lowest level of the new code underneath the TextRenderer which was causing the backing texture to be deleted and re-created each frame. Thanks to Chris Campbell for helping track this down. The upshot is that the TextRenderer class, as well as the new TextureRenderer and Overlay classes, should now be massively faster, especially as the backing texture gets larger.

emzic, could you please try your new code again with a JOGL nightly build dated 1/6 or later and see whether performance is better with your app?
Offline emzic

Senior Devvie





« Reply #12 - Posted 2007-01-06 20:27:38 »

yes, i will try the new nightly build and report back here as soon as possible. thanks!

www.embege.com - personal website
webstart blendinspect - OpenGL BlendingModes visualization.
Offline emzic

Senior Devvie





« Reply #13 - Posted 2007-01-07 12:49:32 »

ok, with the 1/6 nightly the performance is indeed a lot better. there is almost no framerate difference to my older font renderer.

but there seems to be a problem with the uv coordinates algorithm now. it looks like am getting a "random" rectangle out of the font texture.

i have attached a screenhost if that helps you. thanks!

www.embege.com - personal website
webstart blendinspect - OpenGL BlendingModes visualization.
Offline Ken Russell

JGO Coder




Java games rock!


« Reply #14 - Posted 2007-01-07 16:44:09 »

emzic: is it possible for you to email me a jar file containing your app? Clearly I haven't done enough debugging of the TextRenderer in real world cases and I can't guess what's going wrong just from your screenshot.
Offline Matzon

JGO Knight


Medals: 19
Projects: 1


I'm gonna wring your pants!


« Reply #15 - Posted 2007-01-07 18:28:20 »

... I can't guess what's going wrong just from your screenshot.
Sorry for hijacking, but is there a magic screenshot attached? - coz I can't see anything... - is this a rights issue with some user(groups) on the board?

Offline ryanm

Senior Devvie


Projects: 1
Exp: 15 years


Used to be bleb


« Reply #16 - Posted 2007-01-07 18:59:24 »


Abusing Google Base as an image host FTW!
Offline Riven
« League of Dukes »

« JGO Overlord »


Medals: 834
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #17 - Posted 2007-01-07 19:03:56 »

Not there for me either.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline emzic

Senior Devvie





« Reply #18 - Posted 2007-01-07 20:23:50 »

the error is simply caused by the fact that a texture binding occurs before the rendering.

this simple example shows it. just remove the line with tex.bind(); and it should work.
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;

import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLException;

import com.sun.opengl.util.Animator;
import com.sun.opengl.util.j2d.TextRenderer;
import com.sun.opengl.util.texture.Texture;
import com.sun.opengl.util.texture.TextureIO;

public class TextTest extends Frame implements GLEventListener {

   private static final long serialVersionUID = 1L;

   GLCanvas canvas;

   TextRenderer tr;

   Animator animator;

   Texture tex;

   public TextTest() {
      super("TextTest");
      canvas = new GLCanvas();
      canvas.addGLEventListener(this);
      animator = new Animator(canvas);
      animator.start();
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent e) {
            new Thread(new Runnable() {
               public void run() {
                  animator.stop();
                  System.exit(0);
               }
            }).start();
         }
      });
      add(canvas);
      setVisible(true);
      setSize(200, 200);
   }

   public static void main(String[] args) {
      new TextTest();
   }

   public void display(GLAutoDrawable arg0) {
      GL gl = arg0.getGL();
      gl.glClear(GL.GL_COLOR_BUFFER_BIT);
      gl.glLoadIdentity();

      tex.bind(); //comment out this line!
     
      tr.draw("Hello Ken", 10, 100);
   }

   public void displayChanged(GLAutoDrawable arg0, boolean arg1, boolean arg2) {
   }

   public void init(GLAutoDrawable arg0) {
      GL gl = arg0.getGL();
      gl.glClearColor(0, 0, 0, 1);
      gl.glEnable(GL.GL_TEXTURE_2D);

      try {
         tex = TextureIO.newTexture(new File("font.png"), true);
      } catch (GLException e) {
         e.printStackTrace();
      } catch (IOException e) {
         e.printStackTrace();
      }

      tr = new TextRenderer(new java.awt.Font("Verdana", java.awt.Font.BOLD, 20), true , false);
   }

   public void reshape(GLAutoDrawable drawable, int x, int y, int width,
         int height) {
      GL gl = drawable.getGL();
      gl.glViewport(0, 0, width, height);
      gl.glMatrixMode(GL.GL_PROJECTION);
      gl.glLoadIdentity();
      gl.glOrtho(0, width, 0, height, -1, 1);
      gl.glMatrixMode(GL.GL_MODELVIEW);
      gl.glLoadIdentity();
   }
}

www.embege.com - personal website
webstart blendinspect - OpenGL BlendingModes visualization.
Offline kevglass

« JGO Spiffy Duke »


Medals: 210
Projects: 24
Exp: 18 years


Coder, Trainee Pixel Artist, Game Reviewer


« Reply #19 - Posted 2007-01-07 20:28:15 »

Screenshot isn't visible for anyone in the "Java Core" group.

Kev

Offline Ken Russell

JGO Coder




Java games rock!


« Reply #20 - Posted 2007-01-07 21:56:15 »

emzic: sorry, you aren't using the TextRenderer correctly. You need to call beginRendering(int width, int height) before any draw() commands and endRendering() afterward, each frame. Please see the demos.j2d.TestTextRenderer and demos.j2d.FlyingText demos in the jogl-demos workspace. Does doing that make your app work correctly?

All: I've notified the forum admin about not being able to see the attachment; hopefully this can be cleared up.
Offline emzic

Senior Devvie





« Reply #21 - Posted 2007-01-07 22:19:48 »

yes, that solves the problem! thanks.

i was looking in the source and i see that these functions also set up a orthogonal projection. the same which i already have for the GUI rendering. and infact, sometimes i dont want to have the orthogonal projection since i want the text to be in the 3d world. do you think it is possible to add functions that allow the user to set up his viewport and projection matrix himself?

www.embege.com - personal website
webstart blendinspect - OpenGL BlendingModes visualization.
Offline ryanm

Senior Devvie


Projects: 1
Exp: 15 years


Used to be bleb


« Reply #22 - Posted 2007-01-07 23:40:06 »

Screenshot isn't visible for anyone in the "Java Core" group.

So your kind do have a weakness. The time has come for the resistance - rise my comrades, rise up and throw down your false gods!
Offline Ken Russell

JGO Coder




Java games rock!


« Reply #23 - Posted 2007-01-08 03:03:20 »

i was looking in the source and i see that these functions also set up a orthogonal projection. the same which i already have for the GUI rendering. and infact, sometimes i dont want to have the orthogonal projection since i want the text to be in the 3d world. do you think it is possible to add functions that allow the user to set up his viewport and projection matrix himself?

Do you have a suggestion for what this API would look like? In your app, what do you mean by drawing text in the 3D scene -- do you mean drawing text that isn't screen-aligned, or do you mean drawing screen-aligned text at a certain distance from the near plane so 3D objects occlude it? I think it would be straightforward to add APIs to support the latter case, but I'm not sure what the APIs would look like to support the former, at least without doing more of a higher-level, scene graph style API.

You can already change the modelview and projection matrices after you call TextRenderer.beginRendering(); the FlyingText demo does this. However, the (x,y) coordinates you pass to the draw() command then don't work as specified -- the origin (0, 0) or some other specific point is probably the only point for which you can reason about the on-screen location.
Offline emzic

Senior Devvie





« Reply #24 - Posted 2007-01-08 09:17:52 »

i mean the first case that you described. text can be used to put letters on houses, waypoints or name plates above characters.

as you said i could accomplish this by modifying the projection matrix after the beginRendering() but wouldn't that be some unnecessary state changes?

and for the api, it could do the texture caching stuff in the draw() function while omitting the viewport and projection at all, so calling beginDraw and endDraw becomes optional if someone doesnt want to use his own setup, but the one provided by these functions. Smiley

www.embege.com - personal website
webstart blendinspect - OpenGL BlendingModes visualization.
Offline bienator

Senior Devvie




OutOfCoffeeException


« Reply #25 - Posted 2007-01-08 15:42:50 »

>Do you have a suggestion for what this API would look like?
In my opinion state pushing and restoring isn' t necessary in a low level API like JOGL. The method draw(String str, int x, int y) should just draw a String with the offset (x, y) in the current world space with current state. This provides more flexibility (rendering text everywhere in space) and better performance (perhaps the user of the API wants to render more things with the same gl state as the text...).

My suggestion:
-there should be a method beginRendering which just prepares everything to render simple text quads in current gl state
-glPushAttrib and glDisable calles should be moved from beginOrthoRendering and put into the demo app (if needed)
-beginOrthoRendering should call beginRendering after setting up ortho

just my opinion Grin

bienator

Offline Ken Russell

JGO Coder




Java games rock!


« Reply #26 - Posted 2007-01-08 19:20:06 »

i mean the first case that you described. text can be used to put letters on houses, waypoints or name plates above characters.

as you said i could accomplish this by modifying the projection matrix after the beginRendering() but wouldn't that be some unnecessary state changes?

It isn't this simple. The size of the rectangle being drawn in world-space coordinates isn't as well defined as it is when you're rendering screen-aligned, unscaled text, which is basically what the TextRenderer class supports.

If you build a prototype and a small demo showing what you have in mind, we'll be glad to consider including it.

and for the api, it could do the texture caching stuff in the draw() function while omitting the viewport and projection at all, so calling beginDraw and endDraw becomes optional if someone doesnt want to use his own setup, but the one provided by these functions. Smiley

We can separate out the orthographic projection setup, but I'm not sure that doing so really makes sense in the context of this class. The lower-level TextureRenderer can be used for this, though in that case you'd be responsible for the placement of text on the Java 2D managed backing store.

Please help me understand what you want by building a prototype.
Offline Ken Russell

JGO Coder




Java games rock!


« Reply #27 - Posted 2007-01-08 19:27:16 »

In my opinion state pushing and restoring isn' t necessary in a low level API like JOGL. The method draw(String str, int x, int y) should just draw a String with the offset (x, y) in the current world space with current state. This provides more flexibility (rendering text everywhere in space) and better performance (perhaps the user of the API wants to render more things with the same gl state as the text...).

You need to do state pushing and popping unless you want to specify that certain OpenGL states (like the current texture binding) are destroyed by certain APIs, which breaks modularity.

The TextRenderer.draw() method does exactly what you state it should do. It's the beginRendering() / endRendering() methods which push and pop state and modify the projection matrices. Please look at the code.

My suggestion:
-there should be a method beginRendering which just prepares everything to render simple text quads in current gl state
-glPushAttrib and glDisable calles should be moved from beginOrthoRendering and put into the demo app (if needed)
-beginOrthoRendering should call beginRendering after setting up ortho

I'm not in favor of pulling the low-level state setup (binding the background texture, etc.) out of the TextRenderer. There are a couple of things which are absolutely required for correct results such as the binding of the background texture (which is not exposed in the public API for good reason -- it can change over time as the backing store grows). The setup of the projection matrices is another question and I'd be glad to rethink this, but I'd appreciate it if you would build a concrete prototype and show me what you have in mind. I don't know how to specify the behavior of 2D text rotated with an arbitrary 3D orientation in the context of a 3D scene -- there are too many potential problems with differing scale factors between the TextRenderer and the surrounding scene and I think the API would become too complex. I think it's better to use a scene graph like Xith for this case.
Offline bienator

Senior Devvie




OutOfCoffeeException


« Reply #28 - Posted 2007-01-09 00:13:15 »

I think you got me wrong Ken,

JOGL should not handle the 3d rendering, it should be handled by the application. Just provide additionally to the ortho rendering methods, methods which do not transform or rotate anything - just render the with text textured quads.
This enables you to render the text like emzic said everywhere in the scene without duplicated transformations or changes in the projection matrix.

There are a couple of things which are absolutely required for correct results such as the binding of the background texture...
As I said states I had this in mind:
    gl.glPushAttrib(GL.GL_ENABLE_BIT | GL.GL_DEPTH_BUFFER_BIT | GL.GL_TRANSFORM_BIT);
    gl.glDisable(GL.GL_DEPTH_TEST);
    gl.glDisable(GL.GL_CULL_FACE);
    gl.glDisable(GL.GL_LIGHTING);
IMO this should be also done by the application. (the Font's Textures should be of course handled by JOGL)

I will send you the prototype you requested...

dont get me wrong this are just suggestions, i am very happy with the current implementation of JOGL Wink

Offline Ken Russell

JGO Coder




Java games rock!


« Reply #29 - Posted 2007-01-09 00:42:18 »

What should the width and height of the textured quads (in world coordinates) be?
Pages: [1] 2
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

PocketCrafter7 (12 views)
2014-11-28 16:25:35

PocketCrafter7 (7 views)
2014-11-28 16:25:09

PocketCrafter7 (8 views)
2014-11-28 16:24:29

toopeicgaming1999 (74 views)
2014-11-26 15:22:04

toopeicgaming1999 (64 views)
2014-11-26 15:20:36

toopeicgaming1999 (15 views)
2014-11-26 15:20:08

SHC (29 views)
2014-11-25 12:00:59

SHC (27 views)
2014-11-25 11:53:45

Norakomi (32 views)
2014-11-25 11:26:43

Gibbo3771 (28 views)
2014-11-24 19:59:16
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

Resources for WIP games
by CogWheelz
2014-08-01 16:20:17

Resources for WIP games
by CogWheelz
2014-08-01 16:19:50

List of Learning Resources
by SilverTiger
2014-07-31 16:29:50

List of Learning Resources
by SilverTiger
2014-07-31 16:26:06
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!