External elements slowing down canvas - html5-canvas

I am developing a game using several canvases (3) on top of one another. I am close to finishing the game and I haven't yet optimized the performance.
Regardless, my main concern is that the game has performed pretty well so far, but being close to finish I am building a simple web page around the canvas to give a frame to the game. I am talking just putting the title of the game and a few links here and there, but suddenly the game is now choppy and slow!!! If remove those elements everything is smooth again.
The culprits are:
The game title above the canvas (styled with text-shadow).
four buttons below the canvas to redirect to other sites and credits.
Is it possible that this few static elements interfere with the rendering of the game?
Thank you.

Anything with shadows, rounded corners or expensive effects such as blur cost a lot to render.
Modern browsers try to optimize this in various way but there are special cases which they can't get around just like that (updated render engines using 3D hardware can help in the future).
Shadows are closely related to blurring and needs to be composited per frame due to the possibility that the background, shadow color, blur range etc. could change. Rounded corners forces the browser to create an alpha mask instead of doing just a rectangular clip. The browser may cache some of these operations, but they'll add up in the end.
Text Shadow
A workaround is to "cache" the shadowed text as an image. It can be a pre-made image from Photoshop or it could be made dynamically using a canvas element. Then display this instead of the text+shadow.
Example
var ctx = c.getContext("2d"),
txt = "SHADOW HEADER";
// we need to do this twice as when we set width of canvas, state is cleared
ctx.font = "bold 28px sans-serif";
c.width = ctx.measureText(txt).width + 20; // add space for shadow
c.height = 50; // estimated
// and again...
ctx.font = "bold 28px sans-serif";
ctx.textBaseline = "top";
ctx.textAlign = "left";
ctx.shadowBlur = 9;
ctx.shadowOffsetX = 9;
ctx.shadowOffsetY = 9;
ctx.shadowColor = "rgba(0,0,0,0.8)";
ctx.fillStyle = "#aaa";
ctx.fillText(txt, 0, 0);
body {background:#7C3939}
<canvas id=c></canvas>
The canvas element can now be placed as needed. In addition you could convert the canvas to an image and use that without the extra overhead.
Rounded Corners
Rounded corners on an element is also expensive and there are no easy way around this - the corners need to be cut one way or another and question is which method is fastest.
Let browser do it using CSS
Overlay the element with the outer corners covered in the same color as background - clunky but can be fast as no clipping is needed. However, more data need to be composited.
Use a mask in canvas directly via globalCompositeOperation. The chances are this would be the slowest method. Performance tests must be made for this scenario to find out which one works best overall.
Make a compromise and remove rounded corners all together.
Links
Also these could be replaced by clickable images. It's a bit more tedious but also these could be made dynamically using a canvas allowing the text to change ad-hoc.
CSS
I would also recommend experimenting with position: fixed; for some of the elements. When fixed is used, some browsers renders that element separately (gives it its own bitmap). This may be more efficient in some cases.
But do make some performance tests to see what combination is the best for your scenario.

Related

WebGL – Stretching non power of two textures or adding padding

I'm using images of varying sizes and aspect ratios, uploaded through a CMS in Three.js / A-Frame. Of course, these aren't power of two textures. It seems like I have two options for processing them.
The first is to stretch the image, as is done in Three.JS – with the transformation undone when applied to the plane.
The second is to add extra pixels (which aren't displayed) due to custom UVs.
Would one approach be better than the other? Based on image quality, I'd imagine not doing any stretching would be preferred.
EDIT:
For those interested, I couldn't spot a difference between the two approaches. Here's the code for altering the UVs to cut off the unused texture padding:
var uvX = 1;
var uvY = 0;
if(this.orientation === 'portrait') {
uvX = (1.0 / (this.data.textureWidth / this.data.imageWidth));
} else {
uvY = 1.0 - (this.data.imageHeight / this.data.textureHeight);
}
var uvs = new Float32Array( [
0, uvY,
uvX, uvY,
uvX, 1,
0, 1
]);
EDIT 2:
I hadn't set the texture up properly.
Side by side, the non-stretched (padded) image does look better up close – but not a huge difference:
Left: Stretched to fit the power of two texture. Right: Non-stretched with padding
Custom UV's can be a bit a pain (especially when users can modify the texturing), and padding can break tiling when repeating the texture (unless taken very special care of).
Just stretch the images (or let Three.js do it for you). That's what most engines (like Unity) do anyway. There -might- be a tiny bit of visual degradation if the stretch algorithm and texel sampling do not 100% match, but it will be fine.
The general idea is that if your users -really- cared about sampling quality at that level, they'd carefully handcraft POT textures anyway. Usually, they just want to throw texture images at their models and have them look about right... and they will.

THREE.CanvasRenderer rendering issue, circles leave a 'trace' even though setClearColor is set

This has been puzzling me all day. I have put together a very simple holding page for a website that can be viewed here. It has a very simple three.js animated scene as a background. This is based on one of the examples. Then the user moves the mouse to the top of the window the animation responds to the input. The issue is that there seems to be rendering artefact (see image below or try online in above link)
It seems that there is an unwanted 'trace' like effect that I just can't seem to prevent. The clear color is set 'renderer.setClearColor(0xEFEFEF, 1). I have tried 'renderer.autoClear = true' explicitly but still the artefacts show. It seems to happen when the circle is at the very edge of the drawn area when moving vertically.
I have tried searching everywhere but failed to find an answer. Can anyone help?
UPDATE:
I looked at the link provided by WestLangley where the context.arc scaling looked like a possible cause. It turned out that it was not. I was using a value < 1. I tried a value 1 but the artefacts still show.
I believe that since the drawing area changes each frame (due to the sinusoidal nature of the wave animation) and the changing viewing angle of the camera, the area that is cleared is incorrect. The drawing area that is cleared is the drawing area of the current frame (to be rendered), leaving an uncleared area from the previous frame (already rendered and visible) and possibly artefacts. This only occurs when the drawing area is shrinking and the camera is moving in the opposite direction to the reduction in drawing area. I believe, in order for these artefacts to not show, the drawing area of the previous frame should be cleared and not the area of the current frame before drawing. I am still looking at this.
UPDATE 2:
I seem to have verified the drawing area clearance theory from update 1. By adding:
renderer.getContext().canvas.getContext("2d").clearRect(0, 0, renderer.getContext().canvas.width, renderer.getContext().canvas.height);
before the renderer.render(scene, camera) call, the entire canvas was cleared to white. Since the renderer.setClearColor(0xEFEFEF, 1) is set to slightly off-white, one can clearly see the drawing area.
I decided to add a plane with a transparent material to force the drawing area to be full size of the canvas. I am not 100% happy with this but it seems to be a workaround.
var geo = new THREE.PlaneBufferGeometry(1920 * 2, 1080 * 2, 1, 1);
var mat = new THREE.MeshBasicMaterial({ opacity: 0.01 });
var plane = new THREE.Mesh(geo, mat);
The artefacts are banished.

Why does canvas re-render the entire canvas rather than only a specific portion?

From my very limited html5 canvas experience, it seems like the way to animate something is to clear the entire canvas, and draw everything from scratch.
This doesn't seem very performant. I'm wondering why this was the chosen approach?
Is there an alternative? For example, using an object-oriented approach, if you wanted to re-render a tree in the foreground, the system should cache the background, and only rerender that layer.
Your understanding is correct.
Typical canvas apps will completely erase the canvas and redraw objects.
This process works well because Html Canvas is designed with blazingly fast drawing speed.
Unlike object oriented design, the data that draws on the canvas has been completely "flattened".
There is a single data array containing the Red, Green, Blue & Alpha components of all pixels on the canvas.
[
pixel1Red, pixel1Green, pixel1Blue, pixel1Alpha,
pixel2Red, pixel2Green, pixel2Blue, pixel2Alpha,
pixel3Red, pixel3Green, pixel3Blue, pixel3Alpha,
...
]
This means that any color component of any pixel can be accessed with a single jump.
This flat structure also means that if an image needs to be drawn on the canvas, the browser only needs to copy sequential data from the source image directly into sequential data in the canvas pixel array.
In addition, Canvas is hardware accelerated when a GPU is available.
That's the basic technique, yes. You could also clear a specific area of the canvas instead, using clearRect():
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Drawing_shapes
// Clear the specified rectangular area from x, y through width, height.
context.clearRect(x, y, width, height);
In your case of change foregrounds and backgrounds, however, consider modifying the globalCompositeOperation, which enables you to draw shapes under existing shapes:
// Draw new shapes behind the existing canvas content.
ctx.globalCompositeOperation = 'destination-over';
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Compositing
Another useful method is clip(), which allows you to draw shapes as masks:
// Create a circular clipping path.
ctx.beginPath();
ctx.arc(0, 0, 60, 0, Math.PI * 2, true);
ctx.clip();
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Compositing

how to drop shadows under sprites in Top-down view with cocos2d

I just started learning OpenGL and cocos2d and I need an advice.
I'm writing a game in which player is allowed to touch and move rectangles on the screen in a top-down view. Every time a rectangle is touched, it moves up (towards the screen) in z direction and is scaled a bit to look like it's closer than the rest. It drops down to z = 0 after touch ends.
I'd like the risen rectangles to drop shadow under them, but can't get it to work. What approach would you recommend for the best result?
Here's what I have so far.
During setup I turn on the depth buffer and then:
1. all the textures are generated with CCRenderTexture
2. the generated textures are used as an atlas to create CCSpriteBatchNode
3. when a rectangle (tile) is touched:
static const float _raisedScale = 1.2;
static const float _raisedVertexZ = 30;
...
-(void)makeRaised
{
_state = TileStateRaised;
self.scale = _raisedScale;
self.scale = _raisedScale;
self.vertexZ = _raisedVertexZ;
_glowOverlay.vertexZ = _raisedVertexZ;
_glowOverlay.opacity = 255;
}
glow overlay is used to "light up" the rectangle.
After that I animate it using -(void)update:(ccTime)delta
Is there a way to make OpenGl cast the shadow for me using cocos? For example using shaders or OpenGL shadowing. Or do I have to use a texture overlay to simulate the shadow?
What do you recommend? How would you do it?
Sorry for a newbie question, but it's all really new to me and I really need your help
.
EDIT 6th of March
I managed to get sprites with shadow overlay show under the tiles and it looks ok until one tile has to drop shadow on another which has a non-zero vertexZ value. I tried to create additional shadow sprites which would be scaled and shown on top of the other tiles (usually rising or falling down), but I have problems with animation (tile up, tile down).
Why complicate the problem.
Simply create a projections of how the shadow would look like using your favourite graphics editing program and save it as a png. When the object is lifted, insert your shadowSprite behind your lifted object (you can shift it left/right depending on where you think your light source is).
When the user drops the object down, the show can remain under the object and move with it, making it self visible when the item is lifted again.

HTML5 canvas - Cannot clear dirty pixels - Retina related

I have a simple canvas with a mouseover event. As the user moves the mouse, I want to draw a single pixel at the event's x,y coords (in the future it will be more complex than a single pixel). Essentially it's like a custom cursor.
The logic is extremely simple as shown below. Strangely, although I'm cleaning up the old pixel location, there're tiny remnants of the pixel left behind (since I posted this question, I have discovered it's related to the Retina display). I've been able to work around this. Instead of saving 1px and restore 1px, I save 3px and restore 3px. But I don't understand why I need to do this, and in the future when I'm drawing a more complicated cursor, I want the dirty pixel handling to be precise.
Here's a runnable JSFiddle example: http://jsfiddle.net/sbCq3/2/
// cleanup previously drawn pixel
ctx.putImageData(lastImageData, lastImageX, lastImageY);
// save the imageData currently at x,y
lastImageData = ctx.getImageData(x, y, 1, 1);
lastImageX = x;
lastImageY = y;
// draw the dot
var dotData = ctx.createImageData(1, 1);
...
ctx.putImageData(dotData, x, y);
I'm a bit stumped. I'm wondering if it has anything to do with my Retina display. If I draw a single pixel at 5,5, it's a single pixel in the image data - but I can zoom using the DigitalColor Meter (built in zoom tool) and see that single pixel is sub-divided and anti-aliased. Whereas if I view that pixel on a Windows machine it's a nice solid pixel. I haven't tested to see if this problem appears on Windows or non-retina machines yet. (I'm not referring to the normal canvas anti-aliasing problem).
UPDATE: I just tested this on my coworkers non-retina MacBook 17" and it works perfectly fine. So this definitely appears to be related to the Retina display.
I'm not entirely sure, but I think this may have to do with the resolutions of the different screens. If you've ever looked at an html5 canvas on a smartphone, which most have a much greater resolution per sq inch than a monitor, then you'll see that the quality is much degraded because the web browser expands the 100px that you tell it to use to the same physical size, which ends up being around ~130px. I think the same is happening with your retina display because it uses a crazy good resolution. Basically, different screens have different pixel size ratios. To get around this, I added the following to my code.
var PIXEL_RATIO = (function() {
var ctx = document.createElement("canvas").getContext("2d"),
dpr = window.devicePixelRatio || 1,
bsr = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backignStorePixelRatio || 1;
return dpr / bsr;
})();
Now, whenever you make a canvas or draw anything, multiply your constants by the variable PIXEL_RATIO.

Resources