How do I test if the WebGLTexture object is 'complete' ?
Currently I get this message:
[WebGLRenderingContext]RENDER WARNING: texture bound to texture unit 0 is not renderable. It maybe non-power-of-2 and have incompatible texture filtering or is not 'texture complete'
I get this warning because the render-loop is trying to use the texture before its image has finished loading, so how to fix that?
The easiest way to fix that is to make a 1x1 texture at creation time.
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
new Uint8Array([255, 0, 0, 255])); // red
Then when the image loads you can replace the 1x1 pixel texture with the image. No flags needed and your scene will render with the color of your choice until the image has loaded.
var img = new Image();
img.src = "http://someplace/someimage.jpg";
img.onload = function() {
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
// then either generate mips if the image uses power-of-2 dimensions or
// set the filtering correctly for non-power-of-2 images.
setupTextureFilteringAndMips(img.width, img.height);
}
Just for the sake of saving people the trouble of running into the next problem they are most likely going to run into, WebGL requires mips or it requires filtering that doesn't require mips. On top of that it requires textures with dimensions that are a power of 2 (ie, 1, 2, 4, 8, ..., 256, 512, etc) to use mips. So, when loading an image you'll most likely want to setup the filtering to handle this correctly.
function isPowerOf2(value) {
return (value & (value - 1)) == 0;
};
function setupTextureFilteringAndMips(width, height) {
if (isPowerOf2(width) && isPowerOf2(height) {
// the dimensions are power of 2 so generate mips and turn on
// tri-linear filtering.
gl.generateMipmap(gl.TEXTURE_2D);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
} else {
// at least one of the dimensions is not a power of 2 so set the filtering
// so WebGL will render it.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
}
}
To fix that issue, use some boolean value to tell if the image has loaded.
var loaded = false,
texture,
img = new Image();
img.onload = function() {
texture = gl.createTexture();
// . . .
loaded = true;
};
img.src = "path/myimage.jpg";
// render-loop
function render() {
if(loaded) {
// use texture
}
else {
// not loaded yet
}
}
I had this issue when attempting to deploy my HTML5/JS app to an Android phone using Cordova.
At first, I thought my issue was that my spritesheet/texture atlas was too large for the mobile GPU to load. So I batch shrank all the images using ImageMagick's mogrify (mogrify -resize 256x256 *.png), but was still having issues. This step was still necessary tho (as my 8000x8000 .png was too much for phones).
Then I used console.log(navigator.userAgent) to check my browser version and saw that the Chromium used was older than my browser. So I re-installed the Crosswalk plugin and everything is rendering fine.
cordova plugin rm cordova-plugin-crosswalk-webview
cordova plugin add cordova-plugin-crosswalk-webview
Related
I'm converting my game Hold & Drag from Swift (SpriteKit) to Android using LibGDX, I created my 'SpriteKit' api a little clone of the official on Xcode.
I'm looking for optimizing the render of textures especially because I got FPS drop (45-60 /60 fps)
When I don't draw textures I got 60 (the maximum).
I hope you will help me as efficiently as possible!
#Override
public void draw() {
if (texture == null)
return;
Gdx.gl.glEnable(GL20.GL_BLEND);
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
spriteBatch.setTransformMatrix(getTransformMatrix());
spriteBatch.begin();
Color color = spriteBatch.getColor();
color.a = getAlpha();
spriteBatch.setColor(color);
spriteBatch.draw(texture, -size.width / 2, -size.height / 2, size.width, size.height);
spriteBatch.end();
Gdx.gl.glDisable(GL20.GL_BLEND);
}
public List<SKNode> getParents() {
List<SKNode> parents = new ArrayList<>();
if (parent != null) {
parents.add(parent);
parents.addAll(parent.getParents());
}
return parents;
}
public Matrix4 getTransformMatrix() {
List<SKNode> nodes = getParents();
Collections.reverse(nodes);
nodes.add(this);
Matrix4 transformMatrix = new Matrix4();
transformMatrix.idt();
for (SKNode node : nodes) {
transformMatrix.translate(node.position.x + node.origin.x, node.position.y + node.origin.y, 0);
transformMatrix.rotate(0, 0, 1, node.zRotation);
transformMatrix.scale(node.xScale, node.yScale, 0);
transformMatrix.translate(-node.origin.x, -node.origin.y, 0);
}
return transformMatrix;
}
It is slow to do things that cause the sprite batch to "flush", which means it has to issue a number of OpenGL commands and transfer vertex data, etc. A flush occurs when you call spriteBatch.end() but also occurs if you:
Draw something with a different texture instance than the last thing drawn
Change the projection or transform matrices
Enable/disable blending
So you want to organize so you are not triggering a flush on every object you draw. What is typically done is to begin the batch, and then each sprite is drawn at its particular location with one of the spriteBatch.draw() methods that includes all the parameters you want. Like this:
batch.setProjectionMatrix(camera.combined);
batch.begin();
for (GameObject obj : myGameObjects)
obj.draw();
batch.end();
//And in your game object / sprite class
public void draw() {
if (texture == null)
return;
spriteBatch.setColor(1f, 1f, 1f, getAlpha());
spriteBatch.draw(texture, -size.width / 2, -size.height / 2, size.width, size.height);
}
Note that the above assumes you are referring to the same sprite batch instance in every object.
Now, to make it behave more like SpriteKit (I'm assuming since I haven't used it), your objects each need a transform. But you don't want to be calling setTransformMatrix or you will trigger a flush. Instead you can use the Affine2 class instead of Matrix4. It functions just as well for holding transform data for a 2D object. Then you can use spriteBatch.draw(textureRegion, width, height, affine2Transform) to draw it without triggering a flush.
To avoid triggering flushes from using different texture instances, you should use a TextureAtlas and texture regions. You can read up on that in the LibGDX documentation on their wiki.
As an aside, when using SpriteBatch, you do not need to make OpenGL calls to enable and disable blending. That is handled internally by SpriteBatch. Call spriteBatch.enableBlending() instead.
We are using a canvas to draw an image, and require that the image is smoothed.
so we use context.imageSmoothingEnabled in the 2d Canvas context.
We notice that if we use a jpg and smoothing is true, then the image fails to scale correctly on some machines (mainly windows 64bit).
In addition, if smoothingQuality is set to 'low' (rather than medium or high) then it works, but of course, who wants low.
If you modify the code to point to a PNG file, it seems to work ok.
Here is the JS Code
// Grab the Canvas and Drawing Context
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
// Create an image element
var img = document.createElement('IMG');
// When the image is loaded, draw it
img.onload = function () {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.imageSmoothingQuality = "medium";
// set to true and the image fails to scale
ctx.imageSmoothingEnabled = true;
ctx.drawImage(img, 0, 0, 50, 50);
}
// Specify the src to load the image
img.src = "http://i.imgur.com/gwlPu.jpg";
body {
background: #CEF;
}
<canvas id="c" width="800" height="600"></canvas>
Thanks in advance.
I have tried it on a Win10 64bit, Win10 32Bit (64bitCPU) and Win10 32bit on Chrome Beta, & Canary
Can not repeat problems you mention.
The image format (jpeg,png) will not effect the result of any of the smoothing or quality settings (apart from source image quality).
What can make a difference, especially at small scales is the ratio of the scaled down image to the original. Ratios like 1/2 1/4 1/8th will produce far better results than 1/3 2/9 3/19th. When comparing images of different sizes you should compare at same scale ratio rather than same size (if original have different sizes)
Below I used your code and the image you linked to to draw the image at the same size you had. Along the top from left to right is smoothing true from quality high on left to low on right.
The second row is same but smoothing turned off.
Below that is the same but zoomed via copy of canvas and no smoothing to show more details of the differences the setting make (if any).
If as your question outlines this was reproducible it would be a major issue for chrome and would be quickly fixed. I would say it is something very specific, maybe all the graphics cards are the same type and there is a driver issue, or images are cached and some old bad version is appearing.
If you want more help the best you could do is provide some screen shots of the problems, showing the problem and the desired result. That way we can at least narrow it down somewhat.
var ctx = c.getContext('2d');
var img = new Image;
img.src = "http://i.imgur.com/gwlPu.jpg";
img.onload = function () {
ctx.imageSmoothingQuality = "high";
ctx.imageSmoothingEnabled = true;
ctx.drawImage(img, 0, 0,50,50);
ctx.imageSmoothingQuality = "medium";
ctx.drawImage(img, 60, 0,50,50);
ctx.imageSmoothingQuality = "low";
ctx.drawImage(img, 120, 0,50,50);
ctx.imageSmoothingQuality = "high";
ctx.imageSmoothingEnabled = false;
ctx.drawImage(img, 0, 60,50,50);
ctx.imageSmoothingQuality = "medium";
ctx.drawImage(img, 60, 60,50,50);
ctx.imageSmoothingQuality = "low";
ctx.drawImage(img, 120, 60,50,50);
ctx.imageSmoothingQuality = "low"; // turn off all filtering
ctx.imageSmoothingEnabled = false;
ctx.drawImage(c,0,0,170,110,0,120,680,440)
}
body {
background: #CEF;
}
<canvas id="c" width="800" height="600"></canvas>
Following content edited in by OP user2739963
Here is a screen shot of this not working on a machine (We have many of these).
My testbed is able to composite a thousand elements into clips on screen, but I can't extract a bitmap on the fly from an external swf for Starling or for export to png.
(I'm a bit of a code butcher, but have tried lot of variations and have looked through spritesheet generating examples))
I have seen the advice "You can't get bitmapData from a Class only displayObjects (MClip, Sprite)", My line: s = new AssetClass() below seems to address that.
Correct usage of addtoStage when loading external swf
I am able to generate a bitmap of an internal vector movieclip, and save it to a .png using as3corelib.PNGEncoder.as, but I end up with a blank png using clips from the external swf, running this in Flash Pro CC..
paraphrased code:
// swf load done handler:
testBitmap = new Bitmap(new BitmapData(w, h , true, 0x0));
var AssetClass:Class = getDefinitionByName("assetNameFromDatabase") as Class;
var s:MovieClip = new AssetClass(); // AddChild(s) functions properly
var m:Matrix = new Matrix(1, 0, 0, 1, 400, 300);
testBitmap.bitmapData.draw(s, m);
// addChild(testBitmap); or send to PNGEncoder rsult is blank
// internal clip test that does convert to bitmap
var b:MovieClip = new testdMovieClip();
var m:Matrix = new Matrix(1, 0, 0, 1, 200, 300);
testBitmap.bitmapData.draw(b, m);
//----------------------------
Thanks Vesper, Couldn't gfix things with your suggestion, I was actually already adding to a movieClip, just left it out of the simplified code sample, I did try some variates of adding an instance ahead of drawing to bitmap, still getting no results.
//--
*** Amendment: Looks like the libs have embedded "display but don't copy" protect code? can someone verify this.
I am using merged movieclip libraries provided in swf format, (I have individual fla source for the hundreds of resources in the library swfs that I hoped to avoid dealing with, their removing swf import from Flash Pro cc is a further agrrevation) This is public domain material from a retired game.
Using a Trillix trial, I examined the embedded Class files, along with the usual individual asset Class they had this code:
public override function get movieClipData():flash.utils.ByteArray
{
if (bytes == null)
{
bytes = flash.utils.ByteArray(new dataClass());
}
return bytes;
}
{
bytes = null;
}
internal static var bytes:flash.utils.ByteArray=null;
// the classes also have
import flash.utils.*;
import mx.core.*; // as well as embedded package mx files, is that for Flex?
I was looking at creating some code-less external library files for Air/iOS down the road, I may have to learn to ways to auto merge FLAs sooner than later.
I have seen weird behavior of width and height properties of shape/sprite objects while they are not added to display list, they return -100 million or something. So you should add, then draw, then remove. This trick helped me when I wrote a dynamically generated sprite.
testBitmap = new Bitmap(new BitmapData(w, h , true, 0x0));
var AssetClass:Class = getDefinitionByName("assetNameFromDatabase") as Class;
var s:MovieClip = new AssetClass(); // AddChild(s) functions properly
addChild(s); // !
var m:Matrix = new Matrix(1, 0, 0, 1, 400, 300);
testBitmap.bitmapData.draw(s, m);
removeChild(s);
addChild(testBitmap); // now should display properly
In video games only color is applied to help speed up the loading process. After textures are ready, they replace the current colors. Is there a way to do this in WebGL? All the tutorials I've seen so far only show how to load in color or texture (not one after the other).
I'd guess that the buffer for each shape needs to be altered after its texture is fully loaded. I would assume this is keyed via an AJAX call that the texture is available, then applied via some kind of JavaScript function. Does WebGL have a built in way of doing this without a complicated image loading process?
In most games that I've seen with the behavior you describe they'll typically start with either per-vertex coloring or a VERY low res texture and "blend up" to the full texture when it becomes available. That sort of smooth transition is tricky, but if all you want is a quick "pop" from one to the other it shouldn't be too much trouble.
The basic route I would take is to create a vertex buffer that has both texture coord and color information, as well as two different shaders. One shader will use the color information, the other will ignore it and use the texture instead. You would signal the mesh to start using the texture-based one as soon as the texture is ready.
As for detecting the image load, that's not hard at all and you don't even need AJAX for it:
var image = new Image();
image.addEventListener("load", function() {
// Image is done loading, push to texture
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
// Set up any other state needed, such as switching the shader for the mesh
}, true);
image.src = src;
I'm not sure how much more help I can give on this subject without posting really large code blocks, but if you're still struggling I can detail some of the other parts.
The approach I would take is as follows
loadTexture(url, initialColor) {
var tex = gl.createTexture();
// start with a single color.
gl.bindTexture(gl.TEXTURE_2D, tex);
var pixel = new Uint8Array(initialColor);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, pixel);
// start loading the image
var img = new Image();
img.onload = function() {
// when the image has loaded update the texture.
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
gl.generateMipmap(gl.TEXTURE_2D);
}
img.src = url;
return tex;
}
// Load a tree texture, use brown until the texture loads.
var treeTexture = loadTexture("tree.png", [255, 200, 0, 255]);
// Load a water texture, use blue until it loads.
var waterTexture = loadTexture("water.jpg", [0, 0, 255, 255]);
This is how most of the samples on http://webglsamples.googlecode.com work although they all default to blue textures.
You could easily extend that idea to use a solid color, the load a low-res texture, then when that finishes load a high-res texture.
Note: the code above assumes you are loading power-of-2 textures. If not you'll need to setup your texture parameters correctly.
It's actually very easy, without WebGL having any feature specifically for this, this is something that you get for free just from it being a DOM API. When you load images, anyway you have to implement their 'onload' callback as image loading is asynchronous. So just put in that 'onload' callback whatever code needs to be run to switch from the solid color to the texture.
Normally if you were loading an image from a URL you would do the following:
m_image = new Image();
m_image.addEventListener(Event.COMPLETE, image_completeHandler, false, 0, true);
m_image.source = "http://www.example.com/image.jpg";
private function image_completeHandler(event:Event):void
{
// Image content has now loaded, we need to wait for it to validate it's size
m_image.addEventListener(FlexEvent.UPDATE_COMPLETE, image_updateCompleteHandler, false, 0, true);
}
private function image_updateCompleteHandler(event:FlexEvent):void
{
// Do stuff with width / height
}
But, if you set the source to an embedded image class, the complete event doesn't appear to fire. So my question is, how can you get the width / height of an embedded image / swf?
The instatiation of any embedded asset is syncronous (I think the only exception is Loader.loadBytes), so as soon as you do it you can access all its properties:
image = new EmbeddedImage();
trace(image.width, image.height);