ScalaFX canvas poor performance when drawing image with animation timer - scala

I plan to make a rhythm game by using ScalaFX with canvas,
When I try to run the code, I found that it consumes a lot of GPU, and sometimes the frame rate drop at 30 fps, even I only draw one image on the canvas without drawing any animated note, dancer, process gauge, etc.
Below is my code
import scalafx.animation.AnimationTimer
import scalafx.application.JFXApp
import scalafx.scene.Scene
import scalafx.scene.canvas.{Canvas, GraphicsContext}
import scalafx.scene.image.Image
import scalafx.scene.layout.Pane
import scalafx.scene.paint.Color.Green
object MainApp extends JFXApp{
var MainScene: Scene = new Scene {
fill = Green
}
var MainStage: JFXApp.PrimaryStage = new JFXApp.PrimaryStage {
scene = MainScene
height = 720
width = 1280
}
var gameCanvas:Canvas = new Canvas(){
layoutY=0
layoutX=0
height=720
width=1280
}
var gameImage:Image = new Image("notebar.png")
var gc:GraphicsContext = gameCanvas.graphicsContext2D
MainScene.root = new Pane(){
children=List(gameCanvas)
}
var a:Long = 0
val animateTimer = AnimationTimer(t => {
val nt:Long = t/1000000
val frameRate:Long = 1000/ (if((nt-a)==0) 1 else nt-a)
//check the frame rate
println(frameRate)
a = nt
gc.clearRect(0,0,1280,720)
gc.drawImage(gameImage,0,0,951,160)
})
animateTimer.start()
}
how can I improve the performance or is there any better ways to do the same thing without using canvas?

There are a few factors that are potentially slowing down the frame rate:
You are outputting the frame rate to the console every frame. This is a very slow operation, and also can be expected to slow down the frame rate. (This is probably the biggest performance hit.)
You are calculating the frame rate during frame rendering. Philosophically, a good example of Heisenberg's uncertainty principle, since by measuring the frame rate, you're interfering with it and slowing it down... ;-)
You are clearing the entire canvas each time you want to redraw your image, rather than just that part of it taken up the image. (This initially proved not to be a huge factor in my version of your code, but when I disabled JavaFX's speed limit—see update below—it turned out to make a big difference.)
Regarding the frame rate, in the version below, I record the time (in nanoseconds) of the first frame, and count the number of frames drawn. When the application exits, it reports the average frame rate. This is a simpler calculation that doesn't interfere too much with the operations inside the animation handler, and is a good measure of overall performance. (There's going to be considerable variability in the timing of each frame, due to garbage collection, other processes, JIT compilation improvements, etc. We'll try to skip over all of that by looking at the average rate.)
I also changed the code to clear only the region occupied by the image.
I've also simplified your code a little, to make it slightly more conventional in its use of ScalaFX (using the stage member of the main class, for instance, as well as making more use of type inference):
import scalafx.animation.AnimationTimer
import scalafx.application.JFXApp
import scalafx.scene.Scene
import scalafx.scene.canvas.Canvas
import scalafx.scene.image.Image
import scalafx.scene.layout.Pane
import scalafx.scene.paint.Color.Green
object MainApp
extends JFXApp {
// Nanoseconds per second.
val NanoPerSec = 1.0e9
// Height & width of canvas. Change in a single place.
val canvasHeight = 720
val canvasWidth = 1280
// Fixed canvas size.
val gameCanvas = new Canvas(canvasWidth, canvasHeight)
// The image.
val gameImage = new Image("notebar.png")
val gc = gameCanvas.graphicsContext2D
stage = new JFXApp.PrimaryStage {
height = canvasHeight
width = canvasWidth
scene = new Scene {
fill = Green
root = new Pane {
children=List(gameCanvas)
}
}
}
// Class representing an initial frame time, last frame time and number of frames
// drawn. The first frame is not counted.
//
// (Ideally, this would be declared in its own source file. I'm putting it here for
// convenience.)
final case class FrameRate(initTime: Long, lastTime: Long = 0L, frames: Long = 0L) {
// Convert to time in seconds
def totalTime: Double = if(frames == 0L) 1.0 else (lastTime - initTime) / NanoPerSec
def mean: Double = frames / totalTime
def update(time: Long): FrameRate = copy(lastTime = time, frames = frames + 1)
}
// Current frame rate.
private var frames: Option[FrameRate] = None
val animateTimer = AnimationTimer {t =>
// Update frame rate.
frames = Some(frames.fold(FrameRate(t))(_.update(t)))
// Send information to console. Comment out to determine impact on frame rate.
//println(s"Frame rate: ${frames.fold("Undefined")(_.mean.toString)}")
// Clear region of canvas.
//
// First clears entire canvas, second only image. Comment one out.
//gc.clearRect(0, 0, canvasWidth, canvasHeight)
gc.clearRect(0, 0, gameImage.width.value, gameImage.height.value)
// Redraw the image. This version doesn't need to know the size of the image.
gc.drawImage(gameImage, 0, 0)
}
animateTimer.start()
// When the application terminates, output the mean frame rate.
override def stopApp(): Unit = {
println(s"Mean frame rate: ${frames.fold("Undefined")(_.mean.toString)}")
}
}
(BTW: avoid use of var statements in Scala whenever possible. Shared mutable state is unavoidable when using JavaFX/ScalaFX, but Propertys provide much better mechanisms for dealing with it. Try to get into the habit of using val element declarations unless they really, really do need to be vars. And if you do need to use vars, they should nearly always be declared private to prevent uncontrolled external access and modification.)
Benchmarking Java programs is an art form, but clearly, the longer you run each version, the better the average frame rate is going to be. On my machine (with an image of my own) I achieved the following, rather unscientific, results after running the application for 5 minutes:
Clearing entire canvas and writing to console: 39.69 fps
Clearing entire canvas, no output to console: 59.85 fps
Clearing only image, no output to console: 59.86 fps
Clearing just the image, rather than the whole canvas appears to have little effect, and surprised me a little. However, outputting to the console had a huge effect on the frame rate.
Aside from using a canvas, another possibility is to simply position an image within a scene group, and then move it around by changing its co-ordinates. The code to do that is below (using properties to indirectly move the image):
import scalafx.animation.AnimationTimer
import scalafx.application.JFXApp
import scalafx.beans.property.DoubleProperty
import scalafx.scene.{Group, Scene}
import scalafx.scene.image.ImageView
import scalafx.scene.layout.Pane
import scalafx.scene.paint.Color.Green
import scalafx.scene.shape.Rectangle
object MainApp
extends JFXApp {
// Height & width of app. Change in a single place.
val canvasHeight = 720
val canvasWidth = 1280
// Nanoseconds per second.
val NanoPerSec = 1.0e9
// Center of the circle about which the image will move.
val cX = 200.0
val cY = 200.0
// Radius about which we'll move the image.
val radius = 100.0
// Properties for positioning the image (might be initial jump).
val imX = DoubleProperty(cX + radius)
val imY = DoubleProperty(cY)
// Image view. It's co-ordinates are bound to the above properties. As the properties
// change, so does the image's position.
val imageView = new ImageView("notebar.png") {
x <== imX // Bind to property
y <== imY // Bind to property
}
stage = new JFXApp.PrimaryStage {
height = canvasHeight
width = canvasWidth
scene = new Scene {thisScene => // thisScene is a self reference
fill = Green
root = new Group {
children=Seq(
new Rectangle { // Background
width <== thisScene.width // Bind to scene/stage width
height <== thisScene.height // Bind to scene/stage height
fill = Green
},
imageView
)
}
}
}
// Class representing an initial frame time, last frame time and number of frames
// drawn. The first frame is not counted.
//
// (Ideally, this would be declared in its own source file. I'm putting it here for
// convenience.)
final case class FrameRate(initTime: Long, lastTime: Long = 0L, frames: Long = 0L) {
// Convert to time in seconds
def totalTime: Double = if(frames == 0L) 1.0 else (lastTime - initTime) / NanoPerSec
def mean: Double = frames / totalTime
def update(time: Long) = copy(lastTime = time, frames = frames + 1)
}
// Current frame rate.
var frames: Option[FrameRate] = None
val animateTimer = AnimationTimer {t =>
// Update frame rate.
frames = Some(frames.fold(FrameRate(t))(_.update(t)))
// Change the position of the image. We'll make the image move around a circle
// clockwise, doing 1 revolution every 10 seconds. The center of the circle will be
// (cX, cY). The angle is therefore the modulus of the time in seconds divided by 10
// as a proportion of 2 pi radians.
val angle = (frames.get.totalTime % 10.0) * 2.0 * Math.PI / 10.0
// Update X and Y co-ordinates related to the center and angle.
imX.value = cX + radius * Math.cos(angle)
imY.value = cY + radius * Math.sin(angle)
}
animateTimer.start()
// When the application terminates, output the mean frame rate.
override def stopApp(): Unit = {
println(s"Mean frame rate: ${frames.fold("Undefined")(_.mean.toString)}")
}
}
This produces a mean frame rate for me, after 5 minutes of running, of 59.86 fps—almost exactly the same as using a canvas.
In this example, the motion is a little jerky, which could well be caused by garbage collection cycles. Maybe try experimenting with different GC's?
BTW, I move the image around in this version to force something to happen. If the properties don't change, then I suspected that the image would not be updated that frame. Indeed, if I just set the properties to the same value each time, the frame rate becomes: 62.05 fps.
Using the canvas means that you have to determine what is drawn, and how to redraw it. But using the JavaFX scene graph (as in the last example) means that JavaFX takes care of figuring out whether the frame even needs to be redrawn. It doesn't make a big difference in this particular case, but it might speed things up if there are few content differences between successive frames. Something to bear in mind.
Is that fast enough? BTW, there's a lot of overhead in relation to content in this particular example. I wouldn't be at all surprised if adding other elements to the animation had very little impact upon the frame rate. It would probably be best to try it and see. Over to you...
(For another possibility regarding animation, refer to the ColorfulCircles demo that comes with the ScalaFX sources.)
UPDATE: I mentioned this in a comment, but it's perhaps worth highlighting in the main answer: JavaFX has a default speed limit of 60 fps, which also impacts the benchmarking above—and which also explains why the CPU and GPU are not better utilized.
To enable your application to run at the highest possible frame rate (possibly not a great idea if you're looking to maximize battery charge on a laptop, or to improve overall application performance), enable the following property when running your application:
-Djavafx.animation.fullspeed=true
Note that this property is undocumented and unsupported, meaning that it may go away in a future version of JavaFX.
I re-ran the benchmarks with this property set, and observed these results:
Using a canvas:
Clearing entire canvas and writing to console: 64.72 fps
Clearing entire canvas, no output to console: 144.74 fps
Clearing only image, no output to console: 159.48 fps
Animating a scene graph:
No output to console: 217.68 fps
These results change my original conclusions significantly:
Rendering images—and even animating them—using the scene graph is far more efficient (36% better in terms of frame rate) than the best result obtained when drawing the image on the canvas. This is not unexpected, given that the scene graph is optimized to improve performance.
If using the canvas, clearing only the region occupied by the image has a roughly 10% (for this example) better frame rate than clearing the entire canvas.
Refer to this answer for further details about the javafx.animation.fullspeed property.

Related

Unity jagged mouse input detection

I wrote a script that writes Input.mousePosition into a file on every frame. The idea is that I want to identify which button on screen the player is trying to click before actually clicking, from the speed of the mouse basically. However, I ran into data like this:
(1113.0, 835.0, 0.0)
(1113.0, 835.0, 0.0)
(1113.0, 835.0, 0.0)
(1126.0, 835.0, 0.0)
Basically on one frame the x position is one value, a couple of frames later it's changed, but in the middle there is no gradation. While my mouse movement was continuous, if I'm to believe Unity, in the example above I hovered on 1 pixel for 3 frames then jumped 13 pixels to the right in one frame. Why is this? Is there any code to get the actual frame by frame position of the mouse?
EDIT:
Vector2 _lastPosition;
StreamWriter _mouseData;
// Start is called before the first frame update
void Start()
{
_mouseData = new StreamWriter(File.Open("sdata.txt", FileMode.Create));
}
// Update is called once per frame
void FixedUpdate()
{
_mouseData.WriteLine(Input.mousePosition.ToString());
if (Input.GetMouseButtonDown(0))
{
_mouseData.WriteLine("CLICK\n\n");
}
_lastPosition = Input.mousePosition;
}
void OnDestroy()
{
_mouseData.Close();
}
EDIT 2:
I changed the code to the following:
void FixedUpdate()
{
_mouseData.WriteLine(Vector2.SqrMagnitude(new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"))));
if (Input.GetMouseButtonDown(0))
{
_mouseData.WriteLine("CLICK\n\n");
}
}
Now I'm still getting output that's 50% 0-es and non-0 values are sprinkled in on every second row. Exceptions: a few rows where actual values are supposed to be still contain random 0-es. Now, I'm not super concerned about getting less frequent than 1/frame data, but there's no way to distinguish between these false 0-es and actual 0-es when the mouse is not moving, which is an issue.
I cannot find out from your question but I am guessing that you use the FixedUpdate() method which is unreliable in this situation. Update() is advised to use for calls that you want to execute once per frame.
EDIT:
Also, note that it is recommended that you set your application's framerate to a realistic number since it is unlimited by default (on desktop) and your app could be running with so many FPS that it is faster than how often you can sample your mouse input.
You can set the framerate using: Application.targetFrameRate = 60;
Aside from this problem it is generally a good idea to set your framerate to save yourself some headaches. (This is specifically true if you develop for mobile platforms and test on your desktop.)

Using Swift, how do I animate the .setPosition() method of an NSSplitView without visually stretching its contents?

I would like to animate the appearance of a NSSplitViewItem using .setPosition() using Swift, Cocoa and storyboards. My app allows a student to enter a natural deduction proof. When it is not correct, an 'advice view' appears on the right. When it is correct, this advice view will disappear.
The code I'm using is the below, where the first function makes the 'advice' appear, and the second makes it disappear:
func showAdviceView() {
// Our window
let windowSize = view.window?.frame.size.width
// A CGFloat proportion currently held as a constant
let adviceViewProportion = BKPrefConstants.adviceWindowSize
// Position is window size minus the proportion, since
// origin is top left
let newPosition = windowSize! - (windowSize! * adviceViewProportion)
NSAnimationContext.runAnimationGroup { context in
context.allowsImplicitAnimation = true
context.duration = 0.75
splitView.animator().setPosition(newPosition, ofDividerAt: 1)
}
}
func hideAdviceView() {
let windowSize = view.window?.frame.size.width
let newPosition = windowSize!
NSAnimationContext.runAnimationGroup{ context in
context.allowsImplicitAnimation = true
context.duration = 0.75
splitView.animator().setPosition(newPosition, ofDividerAt: 1)
}
}
My problem is that the animation action itself is causing the text in the views to stretch, as you can see in this example: Current behaviour
What I really want is the text itself to maintain all proportions and slide gracefully in the same manner that we see when the user themselves moves the separator: Ideal behaviour (but to be achieved programmatically, not manually)
Thus far in my troubleshooting process, I've tried to animate this outside of NSAnimationContext; played with concurrent drawing and autoresizing of subviews in XCode; and looked generally into Cocoa's animation system (though much of what I've read doesn't seem to have direct application here, but I might well be misunderstanding it). I suspect what's going on is that the .animator() proxy object allows only alpha changes and stretches---redrawing so that text alignment is honoured during the animation might be too non-standard. My feeling is that I need to 'trick' the app into treating the animation as though it's being performed by the user, but I'm not sure how to go about that.
Any tips greatly appreciated...
Cheers

Only scroll in one direction

I have a infinite vertical scrolling bar in unity and I want to suddenly limit the scrolling (in one direction only) when reaching a (variable) threshold.
public GameObject MyScrollRectContent;
public float limit = 300;
void Update () {
if(MyScrollRectContent.transform.localPosition.y >= limit){
//make it ONLY possible to scroll backwards or not beyond the limit and stop elasticity
}
Any ideas how to limit the infinite scrolling?
The simplest thing would be to use the existing ScrollRect. It allows scroll in a defined rectangle. You would have to simulate infinity by setting a really big size in one direction, or find a way to seamlessly reset the position if the user goes too far (difficult to do without seeing the jump but might be possible depending on your content).
If this is not an acceptable solution:
To limit the scrolling, you can just set the position to the limit if the user goes too high:
void Update ()
{
if(MyScrollRectContent.transform.localPosition.y >= limit)
{
var currentPos = MyScrollRectContent.transform.localPosition;
MyScrollRectContent.transform.localPosition = new Vector3(currentPos.x, limit, currentPos.z);
}
}
Now if on top of that you want elasticity, it is a bit more tricky: Somewhere, your script must currently set MyScrollRectContent.transform.localPosition. Instead of doing that, set a targetYPosition. You can imagine this as a point that moves, on which the scrollrect is linked by an elastic. This targetYPosition is contrained by your limit. Then in your update you make the ScrollRectContent follow.
public void OnUserInput(float y) // I don't know how you actually do this. this is an example
{
_targetYposition = y;
if(_targetYposition > limit)
{
_targetYPosition = limit;
}
}
private void Update()
{
var currentPos = MyScrollRectContent.transform.localPosition;
var y =
Vector3.Lerp(currentPos.y,
_targetYPosition, Time.deltaTime*_speed);
MyScrollRectContent.transform.localPosition = new Vector3(currentPos.x, y, currentPos.z);
}
Note that this is a simple example aiming at hinting you in the right direction. You'll have to adapt it to your code. The elasticity will likely not look as you want either, so you might want to modify the Update function to create other effects

Erasing parts of a bitmap in EaselJS using destination-out compositing

I'm having a bit of difficulty getting some functionality to work. I'm trying to create an eraser and erase parts of an image using easelJS. I've seen other people do this, but only erasing other graphics - and when I try to erase an image, I can't get anything to work. If I wanted to erase a bitmap instead of other graphics, is that possible?
I also tried to use the AlphaMaskFilter, but it's giving me the exact opposite of what I'm looking for (it's masking everything, and only revealing what I paint).
var c = createjs, stage, art;
var x, y, listener, color, hue=0;
stage = new c.Stage("test");
var testImg = new c.Bitmap("http://lorempixel.com/output/animals-q-c-640-480-5.jpg");
art = stage.addChild(testImg, new c.Shape());
art.cache(0,0,600,400);
stage.on("stagemousedown", startDraw, this);
function startDraw(evt) {
listener = stage.on("stagemousemove", draw, this);
stage.on("stagemouseup", endDraw, this);
color = c.Graphics.getHSL(hue+=85, 50, 50);
x = evt.stageX-0.001; // offset so we draw an initial dot
y = evt.stageY-0.001;
draw(evt); // draw the initial dot
}
function draw(evt) {
art.graphics.ss(20,1).s(color).mt(x,y).lt(evt.stageX, evt.stageY);
// the composite operation is the secret sauce.
// we'll either draw or erase what the user drew.
art.updateCache(erase.checked ? "destination-out" : "source-over");
art.graphics.clear();
x = evt.stageX;
y = evt.stageY;
stage.update();
}
function endDraw(evt) {
stage.off("stagemousemove", listener);
evt.remove();
}
http://jsfiddle.net/17xec9y5/8/
Your example is only affecting the Shape instance that you have cached. When you use multiple arguments in addChild(), it returns the last added item, so in your sample, the art variable just references the shape. So the image is just below the "painted area" that you are drawing to.
To fix this, create and cache a container instead. A few minor additions:
Once the image loads, update the cache one time (to apply the image).
Then remove the image so it is no longer applied every time you update the cache while drawing.
That's it!
Here is a fiddle:
http://jsfiddle.net/lannymcnie/17xec9y5/9/
Relevant Code:
// Listen for the image load
testImg.image.onload = function() {
cont.updateCache("source-over"); // Update cache once
cont.removeChild(testImg); // Remove image
stage.update(); // Draw the stage to see the image
}
// Create a sub-container that will hold the art and image
var cont = stage.addChild(new c.Container());
art = new c.Shape(); // Art is just the shape
cont.cache(0,0,600,400); // Cache the container instead
cont.addChild(testImg, art);
// Then, later update the container's cache (instead of the art)
cont.updateCache(erase.checked ? "destination-out" : "source-over");

Stage scaling affecting AS3

I have some code that controls a serious of images to produce and 360 spin when dragging mouseX axis. This all worked fine with the code I have used.
I have since had to design for different platform and enlarge the size of the stage i did this by scale to stage check box in the document settings.
While the mouse down is in action the spin works fine dragging through the images as intended but when you you release and start to drag again it doesn't remember the last frame and jumps to another frame before dragging fine again? Why is it jumping like this when all I have done is change the scale of everything?
please see code use to
//ROTATION OF CONTROL BODY X
spinX_mc.stop();
var spinX_mc:MovieClip;
var offsetFrame:int = spinX_mc.currentFrame;
var offsetX:Number = 0;
var percent:Number = 0;
//Listeners
spinX_mc.addEventListener(MouseEvent.MOUSE_DOWN, startDragging);
spinX_mc.addEventListener(MouseEvent.MOUSE_UP, stopDragging);
function startDragging(e:MouseEvent):void
{
// start listening for mouse movement
spinX_mc.addEventListener(MouseEvent.MOUSE_MOVE,drag);
offsetX = stage.mouseX;
}
function stopDragging(e:MouseEvent):void
{
("stopDrag")
// STOP listening for mouse movement
spinX_mc.removeEventListener(MouseEvent.MOUSE_MOVE,drag);
// save the current frame number;
offsetFrame = spinX_mc.currentFrame;
removeEventListener(MouseEvent.MOUSE_DOWN, startDragging);
}
// this function is called continuously while the mouse is being dragged
function drag(e:MouseEvent):void
{
trace ("Drag")
// work out how far the mouse has been dragged, relative to the width of the spinX_mc
// value between -1 and +1
percent = (mouseX - offsetX) / spinX_mc.width;
// trace(percent);
// work out which frame to go to. offsetFrame is the frame we started from
var frame:int = Math.round(percent * spinX_mc.totalFrames) + offsetFrame;
// reset when hitting the END of the spinX_mc timeline
while (frame > spinX_mc.totalFrames)
{
frame -= spinX_mc.totalFrames;
}
// reset when hitting the START of the spinX_mc timeline
while (frame <= 0)
{
frame += spinX_mc.totalFrames;
}
// go to the correct frame
spinX_mc.gotoAndStop(frame);
}
By changing
spinX_mc.addEventListener(MouseEvent.MOUSE_MOVE,drag);
offsetX = stage.mouseX;
to
spinX_mc.addEventListener(MouseEvent.MOUSE_MOVE,drag);
offsetX = mouseX;
I seem to of solved the problem and everything runs smoothly again.