Saucer
JGO n00b  Posts: 37 Medals: 1
|
 |
«
on:
2011-10-29 01:42:44 » |
|
Current solution: LWJGL + extra thread fix (downloads have not been updated with this fix)Hello JGO! This is my first time posting here; I apologize in advance for any mistakes I make. I've been using Game Maker for quite a while, but recently I learned some Java in college so I've been trying to make a game engine in Java (relying further on tutorials and various other sources). I want my engine to use fixed timesteps or some similar method (with graphical interpolation), and want it to be run in a window (or rather, to be capable of being run in either fullscreen or windowed mode as opposed to just one or the other), if possible. But, on some computers the graphics never get rendered as smoothly as, say, a GM game--- moving images sometimes stutter. I realize that much has already been said on this topic, for example in the following links: But I feel like no conclusive solution has yet been discovered, so I'd like to try and consolidate all of the known information on this topic and start another, more language-specific discussion. I've been discussing this with some people over at TIGForums in this topic: We've discussed many things and tried various methods to solve the issue. However, at least on the computers I've tested my programs on, I haven't been able to get smoothness on the level of GM. The only computers that have gotten really close to GM's smoothness are the ones at my university which, if I understand correctly, are really fast. Note: I've mostly tested on Windows computers. Potential causes:1) Hardware. It could just be that the machines I test on are slow, but I doubt this is the case because as far as I know there shouldn't be much reason for them to be slow, and in any case that doesn't explain why GM games still run smoothly on them. It is true that GM games also display some stuttering on the same computers, but it's usually not as noticeable as the stuttering with Java. 2) My code. Of course I may have just written faulty code, but I think, if that were the problem, there probably wouldn't be so many existing discussions about the issue, would there? (Because people would just need one or two resources of this kind to solve their problem, and wouldn't have had these discussions for so long.) EDIT: I am kind of a novice, though. I've been building my program from portions of various examples and tutorials; if there is an error in my code, it may be pretty likely that it comes from faulty implementation of the code from these various sources. Maybe I made an error in fitting all the different pieces into my target framework? EDIT: I prepared several versions of the LWJGL example Cero posted that incorporate different examples of fixed timesteps from various sources. I've bundled together those examples in a single download, along with some other example programs (like a similar Game Maker example) and the source for all the different examples. You can download it here: box.com downloadNote: example_simple.jar and example_variabletimestep.jar are examples without fixed timesteps; they're there for comparison. There are various settings you can toggle within the JARs, like vsync and the usage of Thread.yield and Thread.sleep. More details are contained in the included readme file. 3) Sleeping and timers. This is the kind of problem discussed in this thread. The idea is that sleep() is unreliable, and/or that the precision of available timers is insufficient. Wasn't this latter problem solved by System.nanoTime(), though? a) Potential solution: Running a thread in the background the whole time. I've seen this work on at least one computer, but I think I've also seen it not do much another computer, and in any case it seems a bit hacky to me... 4) Something to do with waiting for vertical retrace. Here are some links related to this topic: a) Potential solution: A request for enhancement. I think some of us have heard that there are or were people trying to incorporate vsyncing capabilities (or something similar) into Java itself: But as far as I know this hasn't been achieved yet. Do any of you know where this effort currently stands? b) Potential solutions with native code. The following links, one of them a JGO thread by Kevin Glass, claim to provide native methods to solve this: But I haven't tried the first solution (I don't really know how to make it work...), and I suspect that the second one might be outdated. Does anyone know how to properly implement these solutions, or know if any current, equivalent solutions have been made, or could be made? c) Potential solution provided over in the XNA forums. Someone has apparently experienced similar problems with XNA, and has provided a supposed solution: This may be a solution that can get around not having access to vsync, but I've had trouble understanding some of the XNA-specific code provided in the link. Does anyone know enough XNA to be able to try an equivalent solution in Java? A Potential General Non-Solution: Maybe we're just being too sensitive to stuttering? Someone in the aforementioned XNA-related link commented: Ignore the GPU clock, and let the CPU clock control the update frequency. Sure, this means you will occassionally have dropped or doubled frames, but as long as the clocks are roughly close, this will be rare, and the results are fine for many games. Especially, people tend to notice the time drift in very simple test apps where they are doing things like just moving a box across the screen one pixel per tick, so they freak out about this, but the artifacts tend to become less obvious as the game becomes more complex, so they are often no problem at all in the finished product. The nice thing about this mode is that it keeps your update logic nice and simple, which is why this is the XNA Framework default (we call it fixed timestep mode). This sounds kind of plausible, but I think there's one thing this doesn't account for: GM running more smoothly than Java. Sorry for the long post! So, where does the future of this issue lie?
|
|
|
|
ra4king
JGO Kernel      Posts: 3160 Medals: 196
I'm the King!
|
 |
«
Reply #1 on:
2011-10-29 02:01:00 » |
|
I have seen screen tearing the worst on crappy computers at my school. What removes the screen tearing is to run the game at or above 60FPS. This is done by using a busy while loop that calls Thread.yield().
|
|
|
|
theagentd
JGO Wizard     Posts: 1392 Medals: 88
|
 |
«
Reply #2 on:
2011-10-29 02:27:50 » |
|
Response to causes: 1. Single core computers (and dual cores to some extent) are more susceptible to stuttering due to other programs hogging resources than quad+ cores. 2 (also touching 4). I've never had any real problems with stuttering unless it's my own fault, usually when I only do some special updating every n:th frame or something like that. In those cases, VSync did not fix the problem, as if you go over 16ms frame time, the frame will have to wait for the next sync, ruining performance while not doing anything to cure the stuttering. I believe the universal solution is to keep the amount of computations you do as constant as possible between frames, in which case you are not the source of the stuttering. Not to mention the mouse lag caused by VSync. 3. The leep fix solves the problem in my experience. 4. See number 2. TIP: Vsync can be used to "prove" stuttering and see if you're not just imagining things. Adjust the workload (create objects, increase number of particles, whatever) so that you get an FPS slightly higher than your screen's refresh rate (often 60 hertz) and then enable VSync. If you get a huge FPS drop (to 30 or even lower) you're having so much frame time jitter that VSync some frames are too slow for the frame time (see number 2 again). If I get my threaded particle test to run at 65 FPS (about 900k particles) and enable VSync performance is mostly constant at 60FPS but occasionally drops a few frames, with very noticeable stuttering in those cases. The reason it's so noticeable for me is because my laptop's screen is extremely bad and slow, and fast moving things never manage to "light" the pixels they cover up fully. If a frame is dropped, all particles remain at their old position for another frame, which will be visible as almost twice as bright particles on my screen. Easily visible as blinking. Wondered what the hell was happening before I figured it out. And what's wrong with calling LWJGL's Display.setVSyncEnabled(true); after creating your display? It works for windowed games too as long as you aren't on 5 year old Intel cards, in which case it doesn't work at all (even for fullscreen). I might try to investigate this more as people seem to seriously have a problem with this. I have seen screen tearing the worst on crappy computers at my school. What removes the screen tearing is to run the game at or above 60FPS. This is done by using a busy while loop that calls Thread.yield().
Completely wrong, screen tearing is most visible in games running at higher than 60 FPS. It's visible at any FPS, but less noticeable at lower <40 FPS (the stuttering hides it a little). Even syncing the game using sleep or (Display.sync() in LWJGL) to achieve 60 FPS or whatever your screen has will NOT remove tearing. The only cure for screen tearing is VSync, and usually at the horrible cost of mouse lag and reduced FPS (unless you use also enable triple buffering, in which case the mouse lag is even worse).
|
There is no god.
|
|
|
Games published by our own members! Go get 'em!
|
|
philfrei
Sr. Member   Posts: 495 Medals: 24
|
 |
«
Reply #3 on:
2011-10-29 03:13:22 » |
|
I thought the big issue with Windows was their choice to use a clock interrupt at something like once every 15msec. The commands Thread.sleep() and System.currentTimeMillis() rely on this signal for their accuracy. However, as theagentd points out, there is what he calls the "leep" solution. I thought this was deemed to be sufficient? (At least for bare boxes moving across the screen with little GUI involvement.) I've been so focused on audio and its interaction with the GUI that I've kind of lost touch with all this. Am looking forward to reading more on this thread. One thing I'll say, though, is that there sure are an effing lot of ways to write less than optimal code, and the type of performance we are looking for doesn't leave a lot of slack. Does the following concept apply at all? In audio, the way the JVM switches back and forth between tasks, the audio signal that gets made is actually assembled a bit ahead of the game, in bunches (and the bunches DON'T necessarily relate to the chosen buffer size). Because of this, real time events such as GUI events, don't "line up" with the audio signal very well. I first brought this up here: http://www.java-gaming.org/topics/an-audio-control-helper-tool-using-a-fifo-buffer/24605/view.html There is a diagram that helps explain. So, I am wondering if there are analogous issues on the graphics end of things.
|
"Life is short, art long, opportunity fleeting, experience treacherous, judgment difficult." 
|
|
|
theagentd
JGO Wizard     Posts: 1392 Medals: 88
|
 |
«
Reply #4 on:
2011-10-29 04:48:51 » |
|
I thought the big issue with Windows was their choice to use a clock interrupt at something like once every 15msec. The commands Thread.sleep() and System.currentTimeMillis() rely on this signal for their accuracy. However, as theagentd points out, there is what he calls the "leep" solution. I thought this was deemed to be sufficient? (At least for bare boxes moving across the screen with little GUI involvement.) I've been so focused on audio and its interaction with the GUI that I've kind of lost touch with all this. Am looking forward to reading more on this thread. One thing I'll say, though, is that there sure are an effing lot of ways to write less than optimal code, and the type of performance we are looking for doesn't leave a lot of slack. Does the following concept apply at all? In audio, the way the JVM switches back and forth between tasks, the audio signal that gets made is actually assembled a bit ahead of the game, in bunches (and the bunches DON'T necessarily relate to the chosen buffer size). Because of this, real time events such as GUI events, don't "line up" with the audio signal very well. I first brought this up here: http://www.java-gaming.org/topics/an-audio-control-helper-tool-using-a-fifo-buffer/24605/view.html There is a diagram that helps explain. So, I am wondering if there are analogous issues on the graphics end of things. I only quickly looked through the thread you mentioned. OpenGL buffers commands and then actually issues them later, if that's something similar. It obviously shouldn't cause lag if the driver handles everything well, but in a well threaded program on a quad-core or something, it might be optimal to leave a single core for the driver to work with.
|
There is no god.
|
|
|
Cero
JGO Neuromancer     Posts: 1050 Medals: 18
|
 |
«
Reply #5 on:
2011-10-29 06:10:14 » |
|
yes Saucer, you are right.
I have tried so many things and I dont have the perfect solution yet.
Cas wrote a gameloop, but when I tried it, it lagged every 3 seconds or so.
Also it depends on your game: I'm making a 2D sidescroller in which case this issue is most noticeable...
Like I said I used so many different loops, but actually, right now, I just enable Vsync (so if vsync works on this machine, its fine) and then sync using LWJGL's Display.sync at 60 This simple way is still one of the more stable ways. I also always use the dead background sleeping thread / windows fix. Don't know yet about Linux, still get screen tearing - but I'm focused on the game itself for now.
I try my game on a lot of machines, because I want to support a lot of machines - and yeah, not easy to get right, especially if there is no vsync
But also: Even though you want to make it perfect, I don't think its necessary - Most of the time the stutters are only like 1 frame every 2-3 seconds, and I doubt a "normal" player will really notice it much - or I hope =D
|
|
|
|
nsigma
Sr. Member   Posts: 342 Medals: 18
|
 |
«
Reply #6 on:
2011-10-29 07:31:26 » |
|
Does the following concept apply at all? In audio, the way the JVM switches back and forth between tasks, the audio signal that gets made is actually assembled a bit ahead of the game, in bunches (and the bunches DON'T necessarily relate to the chosen buffer size). Because of this, real time events such as GUI events, don't "line up" with the audio signal very well. I first brought this up here: http://www.java-gaming.org/topics/an-audio-control-helper-tool-using-a-fifo-buffer/24605/view.html There is a diagram that helps explain. So, I am wondering if there are analogous issues on the graphics end of things. I don't think that's the issue here, because I don't think it's caused by cross-thread communication in that way. If cross-thread communication is an issue, then (as I mentioned in your thread), adding a small but constant time lag between control signal and output can help - a constant delay is actually less noticeable than a shifting one. Incidentally, you shouldn't be getting bunching in that way ... Don't know yet about Linux, still get screen tearing
Is this just with composite window managers (compiz, etc)? There's a range of problems with these that means vsync rarely (if ever) seems to work, whether you try and switch it on or not. Using metacity or similar brings back vsync but loses desktop effects.
|
|
|
|
Cero
JGO Neuromancer     Posts: 1050 Medals: 18
|
 |
«
Reply #7 on:
2011-10-29 08:04:34 » |
|
Don't know yet about Linux, still get screen tearing
Is this just with composite window managers (compiz, etc)? There's a range of problems with these that means vsync rarely (if ever) seems to work, whether you try and switch it on or not. Using metacity or similar brings back vsync but loses desktop effects. Just "marked compiz for complete removal" to be sure - no screen tearing in window mode and "only" a line on the top of the screen (like 40 pixel from the top) when scrolling in fullscreen mode. Still not a fix obviously. Interesting to note is that the stutter is the same as in windows, every 2-3 seconds, 1-2 frames of stutter, on average. Well I also know that one of the programmers who are working on this game too; he doesnt have this problem on his desktop pc at all - and on his laptop, it says 60 but the rendering itself skips frames noticable (pretty sure its some kind of 7 / Aero problem there) But this stutter - I experience it on my desktop quad core machine; it can happen that there is no stutter at all on my 2 Ghz single core laptop It's not predictable =P Ah yes, and this is still not being "fixed" =/
|
|
|
|
nsigma
Sr. Member   Posts: 342 Medals: 18
|
 |
«
Reply #8 on:
2011-10-29 08:19:17 » |
|
Ah yes, and this is still not being "fixed" =/ Out of interest, does the ExtendedBufferCapabilities class mentioned in this thread - http://www.java-gaming.org/index.php/topic,21086. - actually work??? Presume would need to check for and use through reflection to be safe.
|
|
|
|
princec
« League of Dukes » JGO Kernel      Posts: 8089 Medals: 96
Eh? Who? What? ... Me?
|
 |
«
Reply #9 on:
2011-10-29 08:25:36 » |
|
@Cero - do you get stutter with any of our games? Cas 
|
|
|
|
Games published by our own members! Go get 'em!
|
|
Cero
JGO Neuromancer     Posts: 1050 Medals: 18
|
 |
«
Reply #10 on:
2011-10-29 08:38:15 » |
|
@Cero - do you get stutter with any of our games? Cas  I think I only tried ROTT and no i didn't, but there isn't as much scrolling - Well when I played it, I wasn't really looking for it back then But in a sidescroller you have almost constant scrolling of course Since I still use Slick, using your gameloop wasn't as easy - so I might have made some mistakes (with the LWJGL timer or something) But obviously it would incredible if you could make like a simple Ball bouncing example using your gameloop, for us to use as "the solution"
|
|
|
|
princec
« League of Dukes » JGO Kernel      Posts: 8089 Medals: 96
Eh? Who? What? ... Me?
|
 |
«
Reply #11 on:
2011-10-29 09:01:59 » |
|
Ok. I actually used the same code for that Android test too! I'll cobble together a quickie - after Hallowe'en party and lots of cider  Should be funny. Cas 
|
|
|
|
theagentd
JGO Wizard     Posts: 1392 Medals: 88
|
 |
«
Reply #12 on:
2011-10-29 10:17:01 » |
|
Bonk. Stupidly long post again. yes Saucer, you are right.
I have tried so many things and I dont have the perfect solution yet.
Cas wrote a gameloop, but when I tried it, it lagged every 3 seconds or so.
Also it depends on your game: I'm making a 2D sidescroller in which case this issue is most noticeable...
Like I said I used so many different loops, but actually, right now, I just enable Vsync (so if vsync works on this machine, its fine) and then sync using LWJGL's Display.sync at 60 This simple way is still one of the more stable ways. I also always use the dead background sleeping thread / windows fix. Don't know yet about Linux, still get screen tearing - but I'm focused on the game itself for now.
I try my game on a lot of machines, because I want to support a lot of machines - and yeah, not easy to get right, especially if there is no vsync
But also: Even though you want to make it perfect, I don't think its necessary - Most of the time the stutters are only like 1 frame every 2-3 seconds, and I doubt a "normal" player will really notice it much - or I hope =D
VSync does NOT fix stuttering! It even worsens it when it actually appears! The only time it will improve it is if your screen has a better precision timer, in which case it KIND OF will sync to it instead of using your computer's timer, so VSync could improve the timing. However it's so unreliable (Linux, Intel graphics cards) that you should in no way use it without some other kind of syncing method (or using a varying time step). For the last time: Vertical Syncronization is a setting that forces the graphics card to synchronize its update time to screen refreshes. When your 60 hertz screen decides that it is time to renew its content, it will read the current frame the graphics card's front frame buffer (you're drawing to the back buffer as you're doing double buffering). That means that if the front buffer is updated by a new frame while the screen is reading it, it will get part of the old frame (the top part) and part of the new frame (the bottom). This is what's called tearing, as you'll be able to clearly see the "tear" or discontinuity between the two images. By enabling VSync you force the graphics card to only update its front framebuffer when it is not being read by your monitor, eliminating the possibility for tearing. Obviously your graphics card can't overwrite the front buffer when your monitor is reading from it, so it has to wait until the monitor is done reading it. It's like a train station, where your update much catch its train scheduled every 1/60th second. If you miss it, you'll have to wait for the next train. If you enable VSync and you're able to render 50FPS with your hardware, VSync will limit this to 30 FPS. This is due to the fact that you only have a passanger for every other train, forcing you to wait for the next one. I mean, even if you're only 1 ms late for your morning train, you'll still have to wait for the next one (true in both real life and when rendering with VSync xD). If you take even more time you'll have to wait for every 3rd train, and you'll get 20 FPS. Every 4th train = 15 FPS. Your FPS gets rounded down to the nearest (refresh_rate / n) FPS, where n is an integer above 0 (1, 2, 3, ...). If you're able to render at 1000 FPS without VSync, it will still only go as fast as your screen can refresh (in this case 60 FPS), as you only have 60 trains running. "Sure, I know that but how does that worsen stuttering?" Well, if your average frame time is close to 1/60th second (about 16.6667 ms) there is a big risk that your frame will take more to than 1/60th second to render. This could be due to another program using a shared resource (CPU, GPU, whatever), which can push the rendering time for just a single frame to over 1/60th second, causing you to drop a frame. Without VSync, at least half the frame might make it to the screen (which isn't much of an improvement anyway), and you won't be wasting the time until the screen refresh is finished. "So when it works, it gives me perfect timing, right?" NO. The timing might be better than the 15 millisecond precision that Windows manages, but far from perfect. Why? OpenGL buffers commands. While it cannot buffer more than one complete rendered frame at a time (for double buffering), it can buffer rendering commands for several frames. By buffering more frames the driver can ensure that the graphics card always has something to do and increase efficency, similar to how we keep buffers when playing and mixing audio, e.t.c. Your OpenGL commands only block when the command queue is full. As long as your game is GPU limited (or VSync limited, it's the same effect), your CPU will fill the command buffer and then wait until the GPU consumes some of them, which will obviously sync up with the VSync rate and your game will be running pretty synchronized to the refresh rate even though the command buffer causes some jitter. When your CPU is faster than your GPU, VSync doesn't do anything for the timing, but in such a case there is no need to sleep in the first place, so there won't be any timing problems in the first place. Because of the command buffer, it doesn't actually make much sense to measure the rendering time of a frame (the "update delta"), because due to the command buffer, the time you measure is just how much time it took for the last frame, the frame before that, or maybe even the frame before that. It will not move the objects relative to the amount of time it took to actually render them, but by the time it took to submit the commands of the frame, which is also depending on how the buffer is looking at the time (= depending on the commands of previous frames). Again, this is only the case if you're GPU limited. "It solves my stuttering!" Most likely no. "What about micro stuttering?" Micro stuttering is when you get constant stuttering due to frame times varying. I know of two possible causes for it: SLI/Crossfire (two or more graphics cards alternately rendering frames) and workload differing between frames. Micro stuttering is when the game runs it at certain FPS but looks like it's running at a much lower FPS. I've seen very bad micro stuttering due to the nature of alternate rendering with SLI where the two frames are completed at the same time, causing one of them to be displayed for a very short time or not at all as the next frame was ready before a screen refresh, effectively looking more like between 1/2th or 2/3th the FPS you're rendering at. I have seen this myself on my GTX 295, and it's especially bad in Bad Company 2, where it looks like 30-40 FPS when it is running at about 60. In this case, VSync DOES indeed fix the problem, as it gives both graphics card something to synchronize their updates with so that they actually produce frames at roughly constant interval. At the moment, VSync is the only cure for micro stuttering caused by SLI or Crossfire. The other cause is that you do heavy computations occasionally in some frames. This will obviously cause this frame to take more time to render and the game experiences stuttering during those frames. As an example, I only generated my fog of war every 10th frame, but it took about 70ms each time (I had 2000 units >_>). The game was running at up towards 180FPS, but it still looked like it was stuttering due to the uneven distribution of load between frames. In this case, enabling VSync limited the FPS of the frames that just sped by (the ones were I didn't generate the fog of war) while obviously not speeding up the fog of war rendering. With VSync on I got close to 30-40 FPS for the same scene. VSync was making me get an FPS more closer to what I was actually seeing in the first place, and it looked very similar to not having VSync on. "So VSync reduces tearing at least. Why not always use it?" Two words. Input lag. VSync is insanely infamous in shooting games and other games that are dependent on fast input response. The reason is simple. When a game is running at 60 FPS, the game buffers rendering commands several frames ahead. This can be controlled on NVidia cards in the NVidia Control Panel (the confusing setting Maximum "Pre-rendered Frames"). The default value for this setting is 3. Yes, 3 full frames. Check it yourself As long as VSync is on, the command buffer will almost always be full. Frames are consumed at a constant interval, so the input delay can be calculated pretty accurately. delay = (pre_rendered_frames + 1) * frame_time The +1 is because you actually have to render that frame too after buffering it in the command buffer. Example: You have a 60 Hz screen and VSync enabled. The frame time is constant to 1/60th second = 16.666667 milliseconds. The delay is approximately (3 + 1) * 16.666667 = 66.66667ms delay Your cheap USB keyboard and mouse is polled at perhaps 100 Hz, giving you 10ms delay just there. It takes a frame before your game reads the buffered input (actually doing stuff based on the input in the game loop), so that's another frame_time long delay. After rendering you also have to transmit the frame to the monitor. An optimal dual-link DVI cable can transmit data at approximately 8 giga-bit = 1 Giga- byte per second. Ignoring all encoding overhead, e.t.c we still have to transmit the 32-bit color (it's encoded into 10-bit per channel according to Wikipedia so probably 32-bit per pixel). A 1920x1080p screen is 7.91MBytes of data, which will take about 1ms (0.966ms, but hey, I thought it was more xD). Finally the screen has to process it. My laptop represents a worst case scenario with its 17ms delay there, but there are better 2ms screens (like the one I have at home -_-). This is all really simplified, and there are probably lots of other sources of delay, but these should be the worst ones. Total: 10 + 16.66667 + 66.66667 + 1 + 17 = 111.333334 ms delay That's pretty insane. A majority of those numbers are based on frame time, so having an FPS higher than 60 FPS is actually a benefit in fast paced games. For example, disabling VSync on a really good graphics card so the game runs at, say, 120FPS instead, using a 1000Hz polled gaming keyboard and mouse (1ms delay) and a 2ms delay monitor, you can get a much lower delay. In this case, frame_time = 1/120 secs = 8,333333333333. 1 + frame_time + frame_time * 4 + 1 + 2 = 4 + 5 * 8.333333 = 45.66666667 ms delay. This isn't even exactly accurate, as it isn't guaranteed that the command buffer gets completely filled with VSync off. It's most likely slightly less than what those 45.667ms I get by calculating it, maybe 10 ms less at best, but that's a guesstimate. "I've heard triple buffering solves everything." Triple buffering simply adds another fully rendered buffered frame. The reason is that this makes the graphics card always able to render to one of the two backbuffers buffers, making the actual rendering FPS not limited by the screen refresh rate. The cost is even more input delay, Due to all this, the only time I enable it is in games that don't require fast input response, like strategy games (as they usually have a sync time of several hundred milliseconds due to determinism), and when the artifacts of not having it on are too visible. I enable it in Bad Company 2 for more fluent rendering (easier to see things moving > input delay) and in games with extremely visible tearing. For example, in Bioshock there are often blinking lights that look like shit when the you have a clear line between the almost black last frame and the fully lit current frame.
|
There is no god.
|
|
|
Cero
JGO Neuromancer     Posts: 1050 Medals: 18
|
 |
«
Reply #13 on:
2011-10-29 13:20:08 » |
|
Yeah I don't have the mad low level understanding
but with my game, it never stutters with vsync (dont experience any real input lag, not using the mouse anyway), while no vsync results in occasional stutters, like said
I wanted to record it - but then the stutters without vsync behave differently - you have like a big stutter (~800ms) every 3-12 seconds
and cas uses vsync aswell, so if you have THE solution, then pray write THE gameloop / ball bouncing example =D
|
|
|
|
theagentd
JGO Wizard     Posts: 1392 Medals: 88
|
 |
«
Reply #14 on:
2011-10-29 14:32:07 » |
|
That's it. I'm making a test program for this crap tomorrow. Look forward to it.
|
There is no god.
|
|
|
Cero
JGO Neuromancer     Posts: 1050 Medals: 18
|
 |
«
Reply #15 on:
2011-10-29 15:25:31 » |
|
just to show that it's not trivial this is an example from the lwjgl wiki, same problem - it stutters 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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
| import org.lwjgl.LWJGLException; import org.lwjgl.Sys; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.DisplayMode; import org.lwjgl.opengl.GL11; public class LWJGLExample { float x = 400, y = 300; long lastFrame; int fps; long lastFPS; float value = 0.15f; public void start() { try { Display.setDisplayMode(new DisplayMode(800, 600)); Display.create(); } catch (LWJGLException e) { e.printStackTrace(); System.exit(0); } initGL(); getDelta(); lastFPS = getTime(); while (!Display.isCloseRequested()) { int delta = getDelta(); update(delta); renderGL(); Display.update(); Display.sync(60); } Display.destroy(); } public void update(int delta) { x += value * delta; if (x < 100) {x = 100; value = -value; } if (x > 700) {x = 700; value = -value; } updateFPS(); } public int getDelta() { long time = getTime(); int delta = (int) (time - lastFrame); lastFrame = time; return delta; } public long getTime() { return (Sys.getTime() * 1000) / Sys.getTimerResolution(); } public void updateFPS() { if (getTime() - lastFPS > 1000) { Display.setTitle("FPS: " + fps); fps = 0; lastFPS += 1000; } fps++; } public void initGL() { GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); GL11.glOrtho(0, 800, 600, 0, 1, -1); GL11.glMatrixMode(GL11.GL_MODELVIEW); } public void renderGL() { GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); GL11.glColor3f(0.5f, 0.5f, 1.0f); GL11.glPushMatrix(); GL11.glBegin(GL11.GL_QUADS); GL11.glVertex2f(x - 50, y - 50); GL11.glVertex2f(x + 50, y - 50); GL11.glVertex2f(x + 50, y + 50); GL11.glVertex2f(x - 50, y + 50); GL11.glEnd(); GL11.glPopMatrix(); } public static void main(String[] argv) { LWJGLExample fullscreenExample = new LWJGLExample(); fullscreenExample.start(); } } |
|
|
|
|
Saucer
JGO n00b  Posts: 37 Medals: 1
|
 |
«
Reply #16 on:
2011-10-29 15:51:16 » |
|
Wow, so much feedback! I'm having difficulty understanding some of the things being discussed, though--- as you can probably tell, I'm kind of a novice. Note that I've been building my program from portions of various examples and tutorials; if there is an error in my code, it may be pretty likely that it comes from faulty implementation of the code from these various sources. Maybe I made an error in fitting all the different pieces into my target framework? What removes the screen tearing is to run the game at or above 60FPS. This is done by using a busy while loop that calls Thread.yield().
I don't know if this is what you're referring to, but we have tried rendering as fast as possible. I got it to work on one computer with what is reported as "120 FPS" (that's probably not the actual number of frames rendered per second, but it still seemed to help), but it still wasn't perfectly smooth, and I think it was hard on the computer; the fan started going really easily whenever I ran the program like that. Furthermore, on at least two other computers it didn't do much. Another method suggested was to run the program at high update rates, but personally I've already settled on having my updates be performed at a certain rate (60 updates per second). I think Slick2D manages to achieve this, actually, but, again, I haven't yet been able to solve stuttering with it. That's it. I'm making a test program for this crap tomorrow. Look forward to it.
 Alright, here are all the various versions of the basic loop that currently seem promising (note that I'm going for fixed timesteps with graphical interpolation): Chromanoid's latest loop:EDIT: Oops... What's with all the commented code...? 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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
| import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Toolkit; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferStrategy;
public class Game extends javax.swing.JFrame {
private static final long serialVersionUID = 1L; double localTime = 0f;
public Game() { setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); setIgnoreRepaint(true); this.setSize(800, 600); }
public final void start(final double fixedTimeStep, final int maxSubSteps) {
this.createBufferStrategy(2); init(); long start = System.nanoTime(); long step=(long)Math.floor(1000000000d * fixedTimeStep); while (true) { long now = System.nanoTime(); double elapsed = (now - start) / 1000000000d; start = now; internalUpdateWithFixedTimeStep(elapsed, maxSubSteps, fixedTimeStep); internalUpdateGraphicsInterpolated(); while (true) { Thread.yield(); long delta = start + step - System.nanoTime(); if (delta <= 0) { break; } try { Thread.sleep(1); } catch (InterruptedException ex) { } } } }
private int internalUpdateWithFixedTimeStep(double elapsedSeconds, int maxSubSteps, double fixedTimeStep) { int numSubSteps = 0; if (maxSubSteps != 0) { localTime += elapsedSeconds; if (localTime >= fixedTimeStep) { numSubSteps = (int) (localTime / fixedTimeStep); localTime -= numSubSteps * fixedTimeStep; } } if (numSubSteps != 0) { int clampedSubSteps = (numSubSteps > maxSubSteps) ? maxSubSteps : numSubSteps; for (int i = 0; i < clampedSubSteps; i++) { update(fixedTimeStep); } return clampedSubSteps; } return 0; }
private void internalUpdateGraphicsInterpolated() { BufferStrategy bf = this.getBufferStrategy(); Graphics2D g = null; try { g = (Graphics2D) bf.getDrawGraphics(); render(g, localTime); } finally { g.dispose(); } bf.show(); Toolkit.getDefaultToolkit().sync(); } Ball[] balls; BasicStroke ballStroke; int showMode = 0;
protected void init() { balls = new Ball[20]; int r = 20; for (int i = 0; i < balls.length; i++) { Ball ball = new Ball(getWidth() / 2, (getHeight() - 120) / (balls.length + 1f) * (i + 1f) + 60f, 10f + i * 300f / balls.length, 0, r); balls[i] = ball; } ballStroke = new BasicStroke(3); this.addMouseListener(new MouseAdapter() {
@Override public void mouseClicked(MouseEvent e) { showMode = ((showMode + 1) % 3); } }); }
protected void update(double elapsedTime) { for (Ball ball : balls) { ball.x += ball.vX * elapsedTime; ball.y += ball.vY * elapsedTime; if (ball.x > getWidth() - ball.r) { ball.vX *= -1; } if (ball.x < ball.r) { ball.vX *= -1; }
if (ball.y > getHeight() - ball.r) { ball.vY *= -1; } if (ball.y < ball.r) { ball.vY *= -1; } } }
protected void render(Graphics2D g, double interpolationTime) { g.clearRect(0, 0, getWidth(), getHeight()); if (showMode == 0) { g.drawString("red: raw, black: interpolated (click to switch modes)", 20, 50); } if (showMode == 1) { g.drawString("red: raw (click to switch modes)", 20, 50); } if (showMode == 2) { g.drawString("black: interpolated (click to switch modes)", 20, 50); } for (Ball ball : balls) { g.setStroke(ballStroke); if (showMode == 0 || showMode == 1) { g.setColor(Color.RED); g.drawOval((int) (ball.x - ball.r), (int) (ball.y - ball.r), (int) ball.r * 2, (int) ball.r * 2); } if (showMode == 0 || showMode == 2) { g.setColor(Color.BLACK); g.drawOval((int) (ball.x - ball.r + interpolationTime * ball.vX), (int) (ball.y - ball.r + interpolationTime * ball.vY), (int) ball.r * 2, (int) ball.r * 2); } } }
public static class Ball {
public double x, y, vX, vY, r;
public Ball(double x, double y, double vX, double vY, double r) { this.x = x; this.y = y; this.vX = vX; this.vY = vY; this.r = r; } }
public static void main(String args[]) { new Thread() {
{ setDaemon(true); start(); }
public void run() { while (true) { try { Thread.sleep(Integer.MAX_VALUE); } catch (Throwable t) { } } } }; Game game = new Game(); game.setVisible(true); game.start(1 / 120d, 5); } } |
The relevant (?) code in the most promising version of my target framework, based most heavily (I think) on the "Game loops!" tutorial here on JGO, by Eli Delventhal: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
| public void run() { if (myGame != null) myGame.initGame(); final double TIME_BETWEEN_UPDATES = 1000000000.0 / gameSpeed; final int MAX_UPDATES_BEFORE_RENDER = 5; double lastUpdateTime = System.nanoTime(); double lastRenderTime = System.nanoTime(); final double TARGET_FPS = 60; final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS; int lastSecondTime = (int) (lastUpdateTime / 1000000000); running = true; while (running) { double now = System.nanoTime(); int updateCount = 0; while(now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER) { update(); lastUpdateTime += TIME_BETWEEN_UPDATES; updateCount++; updatesPerSecondCount++; } if (now - lastUpdateTime > TIME_BETWEEN_UPDATES) { lastUpdateTime = now - TIME_BETWEEN_UPDATES; } debugDisplay = "" + updateCount; interpolation = Math.min(1.0f, (float) ((now - lastUpdateTime) / TIME_BETWEEN_UPDATES)); render(); lastRenderTime = now; int thisSecond = (int) (lastUpdateTime / 1000000000); if (thisSecond > lastSecondTime) { fps = frameCount; frameCount = 0; updatesPerSecond = updatesPerSecondCount; updatesPerSecondCount = 0; lastSecondTime = thisSecond; } while (now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES) { Thread.yield(); try {Thread.sleep(1);} catch(Exception e) {} now = System.nanoTime(); } } } public void update() { myInput.updateKeys(); if (myGame != null) myGame.update(); } public void render() { g = (Graphics2D) strategy.getDrawGraphics(); g.scale(scale, scale); if (myGame != null) myGame.render(); if (debugOn) { g.setColor(Color.BLACK); g.drawString("Interpolation: " + interpolation, 0, 16); g.drawString("FPS: " + fps, 0, 32); g.drawString("Updates per second: " + updatesPerSecond, 0, 48); g.drawString("updateCount: " + debugDisplay, 0, 64); } frameCount++; g.dispose(); strategy.show(); } |
The relevant (?) code from an attempt to incorporate fixed timesteps into Slick2D (try with and without VSync):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
| @Override public void update(GameContainer container, int delta) throws SlickException { double now = Sys.getTime() * 1000000000.0 / Sys.getTimerResolution(); int updateCount = 0; while(now - lastUpdateTime > timeBetweenUpdates && updateCount < maxUpdatesBeforeRender) { innerUpdate(); lastUpdateTime += timeBetweenUpdates; updateCount++; updatesPerSecondCount++; } if (now - lastUpdateTime > timeBetweenUpdates) { lastUpdateTime = now - timeBetweenUpdates; }
interpolation = Math.min(1.0f, (float) ((now - lastUpdateTime) / timeBetweenUpdates)); int thisSecond = (int) (Sys.getTime() / Sys.getTimerResolution()); if (thisSecond > lastSecondTime) { updatesPerSecond = updatesPerSecondCount; updatesPerSecondCount = 0; lastSecondTime = thisSecond; } deltaDisplay = delta; } public void innerUpdate() { for (int i = 0; i < positions.length; i++) { positions[i] += directions[i] * (SPEED * i + SPEED); if (positions[i] <= 0) directions[i] = 1; else if (positions[i] >= DEFAULT_WIDTH) directions[i] = -1; }
for (int i = 0; i < positions.length; i++) { lastPositions[i] = positions[i]; } }
@Override public void render(GameContainer container, Graphics g) throws SlickException { g.scale(DEFAULT_SCALE, DEFAULT_SCALE); g.setColor(Color.lightGray); g.fillRect(0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT); g.setColor(Color.red); for (int i = 0; i < positions.length; i++) { int x; if (drawInterpolated) { x = (int) ((positions[i] - lastPositions[i]) * interpolation + lastPositions[i]); } else { x = (int) positions[i]; } for (int j = 0; j < 2; j++) { sprite.draw(x, HEIGHTS[j] + 16 * i); } } g.setColor(Color.white); g.drawString("Hello, Slick world!", 16, 100); g.drawString("Delta: " + deltaDisplay, 16, 116); g.drawString("Updates per second: " + updatesPerSecond, 16, 132); g.drawString("Sys.getTime(): " + Sys.getTime(), 16, 148); g.drawString("Sys.getTimerResolution(): " + Sys.getTimerResolution(), 16, 164); g.drawString("" + Sys.getTime() * 1000000000.0 / Sys.getTimerResolution(), 16, 180); g.drawString("Interpolation: " + interpolation, 16, 196); } |
|
|
|
|
princec
« League of Dukes » JGO Kernel      Posts: 8089 Medals: 96
Eh? Who? What? ... Me?
|
 |
«
Reply #17 on:
2011-10-29 16:25:43 » |
|
Hrrrm wait a minute. Who here is running with desktop window composition turned on? Hands up! Cas 
|
|
|
|
theagentd
JGO Wizard     Posts: 1392 Medals: 88
|
 |
«
Reply #18 on:
2011-10-29 16:31:27 » |
|
just to show that it's not trivial this is an example from the lwjgl wiki, same problem - it stutters Is this the oh so dreaded problem of stuttering? You mean updating the game at 63 FPS and expecting it to be look smooth on 60 FPS? Yeah, good example. The stuttering is your fault because you're rendering 3 more frames than you can display each second, so every 1/3rd second you get this small jump, where one frame was never shown. If you actually limited it to 60 FPS properly, you would not have gotten it. Wow, that was hard.
|
There is no god.
|
|
|
Saucer
JGO n00b  Posts: 37 Medals: 1
|
 |
«
Reply #19 on:
2011-10-29 16:32:14 » |
|
Hrrrm wait a minute. Who here is running with desktop window composition turned on? Hands up! Cas  ...Sorry if this is a silly question, but... what's that? Although, whatever it is, could it have no affect on a Game Maker program while still having an affect on our Java programs? One of our issues is that Game Maker games run just fine on the computers that our Java programs don't run smoothly on. Is this the oh so dreaded problem of stuttering? You mean updating the game at 63 FPS and expecting it to be look smooth on 60 FPS? Yeah, good example. The stuttering is your fault because you're rendering 3 more frames than you can display each second, so every 1/3rd second you get this small jump, where one frame was never shown.
I seem to remember encountering this problem when I tried LWJGL. If you actually limited it to 60 FPS properly, you would not have gotten it. Wow, that was hard. Alright, how would we go about doing that?
|
|
|
|
princec
« League of Dukes » JGO Kernel      Posts: 8089 Medals: 96
Eh? Who? What? ... Me?
|
 |
«
Reply #20 on:
2011-10-29 16:35:02 » |
|
Apart from @theagentd's highly astute observations about framerate and LWJGL examples, DWC with OpenGL is entirely happy to occasionally not bother rendering the odd frame for you, causing what appears to be random jitter. Bottom line: on Windows, you need DWC turned off if you're on Vista or 7, or to be running in fullscreen; you need vsync on; and you need to actually sync your update loop to the display refresh rate. If any of these criteria are not met you'll get little glitches. Cas 
|
|
|
|
Cero
JGO Neuromancer     Posts: 1050 Medals: 18
|
 |
«
Reply #21 on:
2011-10-29 16:37:34 » |
|
just to show that it's not trivial this is an example from the lwjgl wiki, same problem - it stutters Is this the oh so dreaded problem of stuttering? You mean updating the game at 63 FPS and expecting it to be look smooth on 60 FPS? Yeah, good example. The stuttering is your fault because you're rendering 3 more frames than you can display each second, so every 1/3rd second you get this small jump, where one frame was never shown. If you actually limited it to 60 FPS properly, you would not have gotten it. Wow, that was hard. It does. And again I didn't write this. It's the one of the LWJGL examples which shows how things are supposed to be done.
|
|
|
|
Saucer
JGO n00b  Posts: 37 Medals: 1
|
 |
«
Reply #22 on:
2011-10-29 16:38:35 » |
|
Apart from @theagentd's highly astute observations about framerate and LWJGL examples, DWC with OpenGL is entirely happy to occasionally not bother rendering the odd frame for you, causing what appears to be random jitter.
Bottom line: on Windows, you need DWC turned off if you're on Vista or 7, or to be running in fullscreen; you need vsync on;
Again, is this something that only affects Java and not Game Maker, then? and you need to actually sync your update loop to the display refresh rate. If any of these criteria are not met you'll get little glitches. I think this is what the XNA forums link was referring to. Could you perhaps provide some example code or pseudo-code to explain how this is done?
|
|
|
|
Cero
JGO Neuromancer     Posts: 1050 Medals: 18
|
 |
«
Reply #23 on:
2011-10-29 16:43:01 » |
|
Disabled "Aero Peek" and "Enable desktop composition". With Vsync the world is fine as always and without it those little stutters turn into tears now.
Not that it contradicts what you said Cas.
But you cannot expect people to have it disabled when it isn't by default, which is obviously a problem here...
|
|
|
|
princec
« League of Dukes » JGO Kernel      Posts: 8089 Medals: 96
Eh? Who? What? ... Me?
|
 |
«
Reply #24 on:
2011-10-29 16:43:28 » |
|
No, this affects all OpenGL games under DWC. DirectX games have better control over how they interact with DWC and appear not to suffer from this issue but I stand to be corrected. Syncing to the display refresh rate is actually best achieved using vsync. It's more accurate than even the nanotimer as though the display might report that it is doing 60hz it might actually be 59.97hz in reality. I use both vsync and a timer as a backup - sometimes vsync is reported as working when it actually isn't. In addition Linux machines tend to not know what their refresh rate is or don't have vsync capability at 60hz (eg. Compiz, that worthless heap of shit currently ruining Linux for everyone, runs at an entirely useless 50hz I'm told). Cas 
|
|
|
|
Saucer
JGO n00b  Posts: 37 Medals: 1
|
 |
«
Reply #25 on:
2011-10-29 16:47:01 » |
|
No, this affects all OpenGL games under DWC. DirectX games have better control over how they interact with DWC and appear not to suffer from this issue but I stand to be corrected. I see, so it may make sense that GM is unaffected. Syncing to the display refresh rate is actually best achieved using vsync. It's more accurate than even the nanotimer as though the display might report that it is doing 60hz it might actually be 59.97hz in reality. I use both vsync and a timer as a backup - sometimes vsync is reported as working when it actually isn't. In addition Linux machines tend to not know what their refresh rate is or don't have vsync capability at 60hz (eg. Compiz, that worthless heap of shit currently ruining Linux for everyone, runs at an entirely useless 50hz I'm told). Cas  But there's no way to do this in Java2D, right. Slick2D has a way, but I heard it only works in fullscreen mode; is that true? In any case turning on VSync in windowed doesn't seem to improve the stuttering much (if at all).
|
|
|
|
Cero
JGO Neuromancer     Posts: 1050 Medals: 18
|
 |
«
Reply #26 on:
2011-10-29 16:54:54 » |
|
Slick2D has a way
Because Slick uses LWJGL, I'm sure Slick setVsync uses only the Display.setVSyncEnabled() of LWJGL. but I heard it only works in fullscreen mode; is that true? In any case turning on VSync in windowed doesn't seem to improve the stuttering much (if at all).
Don't know the technicality, but like Cas said, Display.Display.setVSyncEnabled() really syncs very good. It is definitely very noticeable and different even in window mode.
|
|
|
|
Cero
JGO Neuromancer     Posts: 1050 Medals: 18
|
 |
«
Reply #27 on:
2011-10-29 16:56:11 » |
|
No, this affects all OpenGL games under DWC. DirectX games have better control over how they interact with DWC and appear not to suffer from this issue but I stand to be corrected.
Lets get that LWJGL DirectX binding back in development =P
|
|
|
|
princec
« League of Dukes » JGO Kernel      Posts: 8089 Medals: 96
Eh? Who? What? ... Me?
|
 |
«
Reply #28 on:
2011-10-29 17:00:28 » |
|
If you've got DWC turned off and running in a window, and vsync's on, the only thing left you can do is accurately sync to the hi-res timer. Unfortunately LWJGL's Display.sync() method somehow manages to do this wrongly if you need it to be dead accurate. Try this: 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
| public class Sync { private long timeThen; public Sync() { timeThen = Sys.getTime() & 0x7FFFFFFFFFFFFFFFL; }
public void sync(int frameRate) { long timeNow = Sys.getTime() & 0x7FFFFFFFFFFFFFFFL; if (timeNow < timeThen) { timeThen = timeNow; } long timeNext = (timeThen + Sys.getTimerResolution() / frameRate) & 0x7FFFFFFFFFFFFFFFL; if (timeNext < timeNow) { timeThen = timeNow; return; } do { Thread.yield(); timeNow = Sys.getTime() & 0x7FFFFFFFFFFFFFFFL; } while (timeNow < timeNext); timeThen = timeNext; } } |
Remove the Thread.yield() for ultra-precise timing. Cas 
|
|
|
|
princec
« League of Dukes » JGO Kernel      Posts: 8089 Medals: 96
Eh? Who? What? ... Me?
|
 |
«
Reply #29 on:
2011-10-29 17:03:03 » |
|
One more thing: almost nobody cares about running smoothly when running in a window. It's not how people play real games. Web toys, etc. - that's what people play in windows, with accordingly lower expectations. Cas 
|
|
|
|
|