Java-Gaming.org Hi !
Featured games (83)
games approved by the League of Dukes
Games in Showcase (522)
Games in Android Showcase (127)
games submitted by our members
Games in WIP (590)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Multithreading and VBO  (Read 3094 times)
0 Members and 1 Guest are viewing this topic.
Offline Bonbon-Chan

JGO Coder


Medals: 12



« Posted 2011-09-26 09:09:24 »

Hi,

I want to do some optimization by using the real potential of VBO : multithreading.
The game life cycle is composed of 3 steps :
  • logic
  • CPU -> GPU data transmission
  • rendering

Those steps are organized in 2 threads :

OpenGL ThreadTransmissionRenderingTransmissionRenderingTransmissionRendering
Logic ThreadLogic---Logic---Logic---Logic

So the two thread cycles look like :
Logic thread :
1  
2  
3  
4  
5  
6  
while (!finished)
    {
      waitForFPS();
      logic(time);
      mutex.endOfLogic();
    }


Rendering thread :
1  
2  
3  
4  
5  
6  
7  
8  
while (!finished)
    {
      mutex.waitForLogic();
      transmit();
      mutex.endOfTransmition();
      draw();      
      mutex.endOfDrawing();
    }


I have managed to create the "Mutex" class to synchronized the two thread and it works. But I'm not 100% sure of it and I'm not really happy about it :
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  
/**
 *
 * @author Bonbon-Chan
 */

public class Mutex
{
  private boolean lockLogic = true;
  private Object  logic = new Object();
  private boolean lockRendering = false;
  private Object  rendering = new Object();
 
  public void waitForLogic()
  {
    while(lockLogic)
    {
      synchronized(rendering)
      {
        try { rendering.wait(18); } catch(Exception ex) {}
      }
    }
  }
 
  public void endOfTransmition()
  {
    synchronized(logic)
    {
      lockLogic = true;
      logic.notify();
    }
  }
 
  public void endOfDrawing()
  {
    synchronized(logic)
    {
      lockRendering = false;
      logic.notify();
    }
  }
 
  public void endOfLogic()
  {      
    while(lockRendering) // Wait for end of drawing;
    {
      synchronized(logic)
      {
        try { logic.wait(18); } catch(Exception ex) { ex.printStackTrace(); }
      }
    }
         
    synchronized(rendering) // Start Transmission
    {
      lockLogic = false;
      lockRendering = true;
      rendering.notify();
    }
       
    while(!lockLogic) // Wait for end of transmission
    {
      synchronized(logic)
      {
        try { logic.wait(18); } catch(Exception ex) { ex.printStackTrace(); }
      }
    }
  }
}


What do you think about it ?
Offline sproingie

JGO Kernel


Medals: 202



« Reply #1 - Posted 2011-09-27 00:28:19 »

You have all kinds of spin loops based on testing boolean variables that arent themselves set in a synchronized section and aren't even marked volatile.  This demands the obvious question: how much experience do you have in threads?  If you're not already highly experienced in the topic, you should be using java.util.concurrent instead.

What *specifically* do you aim to get out of this design?
Offline Bonbon-Chan

JGO Coder


Medals: 12



« Reply #2 - Posted 2011-09-27 07:18:54 »

You have all kinds of spin loops based on testing boolean variables that arent themselves set in a synchronized section and aren't even marked volatile.  This demands the obvious question: how much experience do you have in threads?  If you're not already highly experienced in the topic, you should be using java.util.concurrent instead.

Yes, I don't have mush experience in this field. And I had the feeling that is was badly done. I think I have found what I need in java.util.concurrent. Thanks  Grin

What *specifically* do you aim to get out of this design?

To work  Wink. Since I have not a clear idea on what I do, I don't really know what I should be carefull to (I didn't think about the volatile for example...).

Well since there is already tools to do thread synchronization, there is a new version that is far better (I hope to) :
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  
/**
 *
 * @author Bonbon-Chan
 */

public class Mutex
{
  private final Semaphore semaphoreLogic = new Semaphore(1);
  private final Semaphore semaphoreRendering = new Semaphore(1);
  private final CyclicBarrier barrier = new CyclicBarrier(2);

  public Mutex()
  {
    try
    {
      semaphoreLogic.acquire();
      semaphoreRendering.acquire();
    }
    catch(Exception ex) { ex.printStackTrace(); }
  }

  public void waitForLogic()
  {
    try
    {
      semaphoreLogic.acquire();
    }
    catch(Exception ex) {}
  }
 
  public void endOfTransmition()
  {
    semaphoreRendering.release();
  }
 
  public void endOfDrawing()
  {
    try
    {
      barrier.await();
    }
    catch(Exception ex) { ex.printStackTrace(); }
  }
 
  public void endOfLogic()
  {
    try
    {
      barrier.await(); // Wait for end of drawing;
    }
    catch(Exception ex) { ex.printStackTrace(); }
    barrier.reset();

    semaphoreLogic.release(); // Start Transmission

    try
    {
      semaphoreRendering.acquire(); // Wait for end of transmission
    }
    catch(Exception ex) { ex.printStackTrace(); }
  }
}


It works and it should be more safe that what I have done.
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline sproingie

JGO Kernel


Medals: 202



« Reply #3 - Posted 2011-09-27 15:45:17 »

Two semaphores and a CyclicBarrier is still pretty complex ... what I was aiming at was to get you to explain the workings of the system in english, because it's actually kind of hard to intuit from the code.  As far as I can tell, you want rendering to block until the game logic has a frame ready, and you want the game logic to run on a separate core.  Is that about right?
Offline theagentd

« JGO Bitwise Duke »


Medals: 361
Projects: 2
Exp: 8 years



« Reply #4 - Posted 2011-09-27 23:18:38 »

I've found that it is a little easier to do the rendering in the main thread and "thread out" the logic. That way you'll have a little less trouble with the GL context during startup.

Myomyomyo.
Offline Bonbon-Chan

JGO Coder


Medals: 12



« Reply #5 - Posted 2011-09-28 06:52:12 »

Two semaphores and a CyclicBarrier is still pretty complex ... what I was aiming at was to get you to explain the workings of the system in english, because it's actually kind of hard to intuit from the code.  As far as I can tell, you want rendering to block until the game logic has a frame ready, and you want the game logic to run on a separate core.  Is that about right?

Logic and drawing are done at the same time.
When Logic and drawing are finished, CPU->GPU data transferts start.
Then logic and drawing wait that transferts are finished to start again.

Quote
I've found that it is a little easier to do the rendering in the main thread and "thread out" the logic. That way you'll have a little less trouble with the GL context during startup.

It is what I am doing.

Edit :

Sproingie, you are right there was a semaphore that was useless :
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  
/**
 *
 * @author Bonbon-Chan
 */

public class Mutex
{
  private final Semaphore semaphoreRendering = new Semaphore(1);
  private final CyclicBarrier barrier = new CyclicBarrier(2);

  public Mutex()
  {
    try
    {
      semaphoreRendering.acquire();
    }
    catch(Exception ex) { ex.printStackTrace(); }
  }
 
  public void endOfTransmition()
  {
    semaphoreRendering.release();
  }
 
  public void endOfDrawing()
  {
    try
    {
      barrier.await();
    }
    catch(Exception ex) { ex.printStackTrace(); }
  }
 
  public void endOfLogic()
  {
    try
    {
      barrier.await(); // Wait for end of drawing;
    }
    catch(Exception ex) { ex.printStackTrace(); }
    barrier.reset();

    try
    {
      semaphoreRendering.acquire(); // Wait for end of transmission
    }
    catch(Exception ex) { ex.printStackTrace(); }
  }
}


with :
1  
2  
3  
4  
5  
6  
while (!finished)
    {
      waitForFPS();
      logic(time);
      mutex.endOfLogic();
    }


1  
2  
3  
4  
5  
6  
7  
while (!finished)
    {
      mutex.endOfDrawing();
      transmit();
      mutex.endOfTransmition();
      draw();
    }
Offline sproingie

JGO Kernel


Medals: 202



« Reply #6 - Posted 2011-09-28 15:08:23 »

You can do this all with a single semaphore with two permits.  The render and logic threads acquire one permit each, and the transfer acquires two permits.  It is possible for the transfer thread to get starved that way though, so a CyclicBarrier might in fact be a superior solution there.

However, this three-thread design of yours doesn't buy you anything that you don't get with two threads, since you have your other two threads blocking while the data transfer occurs anyway.  Just do your data transfers between renders synchronously in your render thread, which will also relieve you of the problem of handing off your opengl context between the threads.  

If the logic thread needs to notify the data transfer that data is available, I recommend actually pushing that data over a Queue.  If you really must use shared state (and trust me you don't need it), then you can synchronize on a single Semaphore with a single permit, using tryAcquire() in the transfer section.
Offline Bonbon-Chan

JGO Coder


Medals: 12



« Reply #7 - Posted 2011-09-29 06:51:59 »

It is really difficult to be clear  Grin. I DO use only two threads (yes I don't see the point to have a 3rd thread).

I will take a look on how a semaphore with several permit works.
Offline princec

« JGO Spiffy Duke »


Medals: 421
Projects: 3
Exp: 16 years


Eh? Who? What? ... Me?


« Reply #8 - Posted 2011-09-29 10:07:37 »

I think you're doing something here with Java that you're actually supposed to be doing with OpenGL. You shouldn't be using two threads to do this, as such - you should instead consider "Java" being the thing filling up the data at one end and "OpenGL" sucking the data out of the other end. You should arrange a bunch of VBOs in a circular buffer and fill them up up until OpenGL eventually blocks you when you try and map the next one along - meaning your filling is going faster than the rendering.

The next thing you do is double-buffer (or even triple buffer!) your entire world state. This is where you have one thread doing logic on the current world, and one thread doing rendering on the "backbuffer" world. When the logic thread has finished doing logic for a frame, it flips the buffers - this is the point at which you may be blocked if the rendering thread is still rendering. This is where triple buffering could help you.

Just bear in mind it requires you have a deep copy of the entire game state for each buffer.

Cas Smiley

Offline Bonbon-Chan

JGO Coder


Medals: 12



« Reply #9 - Posted 2011-09-29 13:30:08 »

I don't really get your point.
I do use "double buffering". My logic work on a copy of the world (cpu side). And my rendering on another copy of it (gpu side). Well I don't use flipping but copy it that case (which is not a part that it takes a lot of time in some tests I have done).

Has I understand you, OpenGL call has to be done on the two threads : filling VBO on one and rendering call on the other.
Meaning that one call will not block the other (or I don't see the point). if so is it true for all video card, and for OpenGL and OpenGL ES ?
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline theagentd

« JGO Bitwise Duke »


Medals: 361
Projects: 2
Exp: 8 years



« Reply #10 - Posted 2011-09-29 13:41:05 »

Using multiple threads is indeed a good idea in this case. By "filling VBOs", I suppose you mean filling ByteBuffers with data, and then send them in the rendering thread? Or are you using a shared context and all that stuff? As filling a ByteBuffer is relatively slow, this it is indeed a good idea to multithread this part.

Myomyomyo.
Offline princec

« JGO Spiffy Duke »


Medals: 421
Projects: 3
Exp: 16 years


Eh? Who? What? ... Me?


« Reply #11 - Posted 2011-09-29 13:46:39 »

Filling VBOs is not slow - it's probably the fastest thing you can do in Java. You need to fill the VBOs on the OpenGL rendering thread. Filling blocks when OpenGL can't handle any more in the call to map a VBO that the GPU is still trying to read from. Filling uses the "backbuffer" world copy (Bonbon, you have it right, and deep copy is probably better than flipping as sooner or later someone's going to have to copy all that data and doing it all in one go is vastly more efficient).

To my knowledge no OpenGL ES GPUs actually use dedicated VRAM so your performance with VBOs on them is not going to be stellar. In fact for my ES sprite engine I'm currently fiddling with I am simply using two giant vertex arrays (not VBOs) and flip between them each frame, though I'm not doing anything fancy using a second world copy and separate logic thread because half the devices I'll be looking at have only one core anyway.

Cas Smiley

<edited to fix error>

Offline Spasi
« Reply #12 - Posted 2011-09-29 17:57:22 »

You can avoid any kind of multi-threading or double-buffering by using VBO "orphaning". It's the simplest and most efficient way to stream data to the GPU without any synchronization. More details here, make sure you follow the links to the OpenGL forum and read the posts by Rob Barris. Feel free to try out a simple implementation from the LWJGL test package, class org.lwjgl.test.opengl.sprites.StreamVBO.
Offline princec

« JGO Spiffy Duke »


Medals: 421
Projects: 3
Exp: 16 years


Eh? Who? What? ... Me?


« Reply #13 - Posted 2011-09-29 18:32:21 »

I tried the orphaning technique but found it can be a bit unreliable in some implementations. But now I just use a circular ring buffer of VBOs and fill them up one at a time until OpenGL stops me from mapping the next one in turn, which means I've overtaken the GPU. My game's single threaded, and OpenGL does everything via the VBOs asynchronously. Perfect Smiley

Cas Smiley

Offline gouessej
« Reply #14 - Posted 2011-09-29 21:26:48 »

I tried the orphaning technique but found it can be a bit unreliable in some implementations. But now I just use a circular ring buffer of VBOs and fill them up one at a time until OpenGL stops me from mapping the next one in turn, which means I've overtaken the GPU. My game's single threaded, and OpenGL does everything via the VBOs asynchronously. Perfect Smiley
I did something very similar in order to succeed in drawing a mesh which would occupy more than the maximum size of a VBO. Forcing the reallocation of the VBO very often is a bad idea, it really depends on the implementation of the driver. Personally, I used a few VBOs and more direct NIO buffers. At each rendering call, I looped on the content of these NIO buffers, I copied as much data as possible into the VBOs, I used the content (glDrawArrays), I copied, I used the content, and so on... You can even improve this method by using several sets of buffers in a hierarchy, for example to use the disk as a cache.

Offline theagentd

« JGO Bitwise Duke »


Medals: 361
Projects: 2
Exp: 8 years



« Reply #15 - Posted 2011-09-30 03:06:37 »

To my knowledge no OpenGL GPUs actually use dedicated VRAM so your performance with VBOs on them is not going to be stellar.
False. My Nvidia GTX 460m definitely stores VBOs on the GPU. That is kind of the main point of them.
Proof: My level renderer allocates VBOs for each block/subsection only when they are first seen. A 2048x2048 map only needs about 100MB of VRAM when started (zoomed in), but when I zoom out fully forcing all VBOs to be generated it goes up to 1.5GB. I'm not allocating any textures or anything like that. VRAM usage measured with GPU-Z.

Myomyomyo.
Offline princec

« JGO Spiffy Duke »


Medals: 421
Projects: 3
Exp: 16 years


Eh? Who? What? ... Me?


« Reply #16 - Posted 2011-09-30 11:34:20 »

Sorry, I meant to say OpenGL ES not OpenGL there.

Cas Smiley

Pages: [1]
  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.

trollwarrior1 (29 views)
2014-11-22 12:13:56

xFryIx (71 views)
2014-11-13 12:34:49

digdugdiggy (50 views)
2014-11-12 21:11:50

digdugdiggy (44 views)
2014-11-12 21:10:15

digdugdiggy (38 views)
2014-11-12 21:09:33

kovacsa (62 views)
2014-11-07 19:57:14

TehJavaDev (67 views)
2014-11-03 22:04:50

BurntPizza (64 views)
2014-11-03 18:54:52

moogie (80 views)
2014-11-03 06:22:04

CopyableCougar4 (80 views)
2014-11-01 23:36:41
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!