Closed tconkling closed 5 years ago
Damn, so this one has bitten you, too. Sorry to hear about those troubles! 😣
You're right, a workaround would be a huge help, and I'd be willing (while holding my nose) to add something like that to Starling — however, how do you plan to check what's inside the texture? There is no way to read pixels from the texture, after all. Well, we could draw it to the back buffer and use "context.drawToBitmapData", but that might lead to flickering on the back buffer — at least I sometimes noticed this in the past. Maybe it's worth a shot; I don't see an easier way.
Nevertheless, I'd like to make another attempt to get this to Adobe, in parallel with you making the pull request. I've got a few questions for you to get this started.
Thanks a lot in advance, Tim!
Yeah, my plan is to do something along these lines (using the technique described by FatRedBird, in this thread: https://github.com/Gamua/Adobe-Runtime-Support/issues/25)
var maskColor:uint = 0xFF000000;
var emptyColor:uint = 0x00000000;
var tex :Texture = Texture.fromBitmapData(sourceBitmap, false, false, _scale);
if (_validateAfterCreation) {
var bounds1:Rectangle = sourceBitmap.getColorBoundsRect(maskColor, emptyColor, false);
var renderBitmap :BitmapData = new BitmapData(sourceBitmap.width, sourceBitmap.height, true);
var valid :Boolean;
for (var ii :int = 0; ii < 3 && !valid; ++ii) {
var image :Image = new Image(tex);
image.drawToBitmapData(renderBitmap);
var bounds2:Rectangle = renderBitmap.getColorBoundsRect(maskColor, emptyColor, false);
valid = bounds1.equals(bounds2);
if (!valid) {
tex = Texture.fromBitmapData(sourceBitmap, false, false, _scale);
}
}
}
if (!valid) { throw an error or log a warning; }
(Edited to add: I haven't tested the above code yet, but I'll be integrating it into my game today. I'll let you know if it reliably detects & fixes the bug.)
An update, after a day spent working on this:
I ended up using this (really crappy) code for texture validation:
public static function createFromBitmap (sourceBitmap :BitmapData, scale :Number, validate :Boolean, name :String = null) :Texture {
if (!validate) {
return Texture.fromBitmapData(sourceBitmap, false, false, scale);
}
const MASK_COLOR :uint = 0xFF000000;
const EMPTY_COLOR :uint = 0x00000000;
// var srcBounds :Rectangle = sourceBitmap.getColorBoundsRect(MASK_COLOR, EMPTY_COLOR, false);
var renderBitmap :BitmapData = new BitmapData(sourceBitmap.width, sourceBitmap.height, true);
var tex :Texture = null;
var valid :Boolean = false;
for (var ii :int = 0; ii < 3 && !valid; ++ii) {
if (tex != null) {
tex.dispose();
}
tex = Texture.fromBitmapData(sourceBitmap, false, false, scale);
var image :Image = new Image(tex);
image.drawToBitmapData(renderBitmap, EMPTY_COLOR, 0.0);
// var renderBounds :Rectangle = renderBitmap.getColorBoundsRect(MASK_COLOR, EMPTY_COLOR, false);
// valid = renderBounds.equals(srcBounds);
valid = compareBitmaps(sourceBitmap, renderBitmap);
if (!valid) {
log.warning("Texture doesn't match source bitmap", "name", name, "tries", ii + 1);
}
}
renderBitmap.dispose();
return tex;
}
private static function compareBitmaps (src :BitmapData, dst :BitmapData) :Boolean {
if (src.width != dst.width || src.height != dst.height) {
return false;
}
for (var yy :uint = 0; yy < src.height; ++yy) {
for (var xx :uint = 0; xx < src.width; ++xx) {
var srcPx :uint = src.getPixel32(xx, yy);
var dstPx :uint = src.getPixel32(xx, yy);
if (Color.getAlpha(srcPx) != 0 && srcPx != dstPx) {
return false;
}
}
}
return true;
}
(I'm comparing the two bitmaps pixel by pixel, rather than using the BitmapData.getColorBoundsRect
technique, because I cannot get getColorBoundsRect
to behave as expected. I'm using it to find the bounds of the non-transparent pixels in both bitmaps, but I'm getting unequal bounds results with this code even on platforms that load textures properly. So either getColorBoundsRect
is broken or my understanding of it is.)
BUT: with this code in place, I never trigger the "Texture doesn't match source bitmap" warning. A true heisenbug. Does the drawToBitmapData
possibly prevent the error from occurring? Is there a race condition in AIR texture creation/upload on Android? Did I just get unlucky in the dozen or so times I ran the code with this "fix" in place? Who knows. But removing the above code causes the issue to recur.
Thanks a lot for all the additional information, Tim, and for trying out that workaround code!
That said, if the existing test case that's attached to the Adobe Tracker bug isn't enough for their purposes [...]
Which test case do you mean? Unfortunately, there is no test case attached — despite me constantly talking about it, no one ever posted an actual sample project that's confirmed to reproduce the issue. If you'd be able to do that, you'd be my personal (Anti?) hero! :wink:
I must admit I only just read about that getColorBoundsRect
method — somehow, I never noticed it before. So I can't say if you're using it the right way (although from the documentation, I would also have expected the code you wrote to work).
However, if this continues to not pop up while that code is included, this might be a great pointer as to the internal reason for the bug. I agree: this sounds like a race condition triggered by the texture creation code. Something inside image.drawToBitmapData
must prevent it from happening.
You're right. I could've sworn there was sample code that reproduced the bug on affected devices; maybe in the original bug that was lost during the bugbase switchover? Regardless, I'll work on creating a test case today.
I've created a small project that reliably reproduces the bug on my tablet. It's here: https://github.com/tconkling/AIRAndroidCroppedTextures. There's a video of my repro'ing the bug here: https://www.youtube.com/watch?v=v0M1Z00_rUU&t=27s. (This is also linked in the Github project.)
Thanks a lot for the big efforts on getting this reproduced with sample code and video! That will be a HUGE help to get this looked into and hopefully fixed. I'll post the links to those resources to all relevant reports and will give Adobe another heads-up. Depending on the feedback I'm getting, I'll also decide on including that workaround code in Starling.
I'll keep you updated, Tim!
Great, thanks Daniel
@PrimaryFeather - Adobe's updated the bug to say that they can repro it and are working on a fix.
In the meantime - as my game is due to ship in the early new year and Adobe AIR releases are months apart - I need to make do with AIR 28 (which doesn't fix the bug). It turns out my workaround, above, was broken in a boneheaded way: in compareBitmaps
, I'm assigning dstPx
from the source bitmap, so the comparison doesn't do anything.
I've fixed that, but I'm having a couple issues with DisplayObject.drawToBitmapData
: (You may like a separate bug for these issues, which I'm happy to create - let me know)
DisplayObject
's scale by the inverse of this, e.g.:var textureImage :Image = new Image(texture);
textureImage.scale = texture.scale / Starling.current.contentScaleFactor;
var bitmap :BitmapData = textureImage.drawToBitmapData();
(I think this second issue can be overcome for the purposes of working around this texture bug, as we only need to compare the bottom-most row of non-transparent pixels in the source bitmap to the same row in the rendered bitmap - bugged textures are always vertically cropped at an arbitrary offset from their bottom.)
@PrimaryFeather - Adobe's updated the bug to say that they can repro it and are working on a fix.
Hurray, that's great news! Let's hope they get to the root of this issue.
Texture scaling and stage contentScaleFactor seem to be improperly handled.
Could you send me a complete example of the problem you're seeing? I can't quite get to reproduce it. Beware, though, that the "BitmapData" itself does not have any scale factor, so you need to take the different coordinate system into account when accessing it — or assign the correct scale factor when you convert it back to a texture.
The resulting texture will be cropped if it's bigger than the context 3D back buffer.
Yes, that's currently a limitation. Since stage3D only supports reading back pixels from the back buffer (not, say, a render texture), the drawToBitmapData
method draws to the back buffer as it's currently configured for the application. I'll have to try if it's possible to temporarily resize it beyond the bounds of the current application.
In any case, as long as there is no proper workaround, I will add this information to the docs. Thanks for the reminder!!
@PrimaryFeather, re: complete example -- do you mean a working application, or simply a screenshot of what I'm seeing alongside a code snippet?
Also, it turns out - for my test devices at least - that it's the bitmap loading that is failing, and not texture creation. I wrote more about it here, https://github.com/Gamua/Adobe-Runtime-Support/issues/25. So my findings are significantly different from @FatRedBird's (whose results I couldn't replicate), and I wonder if he was simply mistaken or if there are in fact multiple bugs here?
do you mean a working application, or simply a screenshot of what I'm seeing alongside a code snippet?
Screenshot (or explanation text) + code snippet would be totally sufficient!
Also, it turns out - for my test devices at least - that it's the bitmap loading that is failing, and not texture creation.
Oh wow, so it is the bitmap loading that's the problem. Some users in the old forum thread suspected something like that, but we never managed to proof this via an example. You're right, this could mean that we're looking at two different things here. On the other hand, the phenomenon is so similar ... hm.
BTW, I'm happy to hear you found a workaround that sounds feasible for the time being! Hopefully it finally gets fixed at its root — thanks to you, it's now possible — but it's good that this isn't a show stopper any longer.
Sure - here's a function that takes a texture, draws it onscreen in an untransformed Image
, and then renders that Image to a bitmap with the same dimensions as the texture, creates a new texture from the bitmap, and draws that to the screen next to the original image:
private function drawTexture (tex :Texture) :void {
// draw the texture
var texImage :Image = new Image(tex);
// render the texture to a bitmap, turn that into a new texture, and draw that
var renderBitmap :BitmapData = new BitmapData(tex.nativeWidth, tex.nativeHeight, true);
texImage.drawToBitmapData(renderBitmap);
var renderTex :Texture = Texture.fromBitmapData(renderBitmap, false, false, tex.scale);
var bitmapImage :Image = new Image(renderTex);
// show the two images onscreen (snip)
}
Here are the results with a sample texture from my game. My textures all have a scale of 2, and at the game's default resolution, 1024x768, Starling.contentScaleFactor
is also 2 -- the texture scale and contentScaleFactor are equal. Here's what that looks like (original texture on the left, rendered-to-bitmap texture on the right):
When I fullscreen my game on my 2560x1440 monitor, the game's updated viewPort results in a Starling.contentScaleFactor
of 4. Here's the results of running the same function when contentScaleFactor is larger than texture scale (this screenshot and its contents are larger than the previous, but this is simply because the game is scaled up to account for the big jump in resolution):
(If the source texture has a larger scale than Starling.contentScaleFactor
-- if I were to run the game at a super tiny resolution -- that texture will be shrunken in the resulting bitmap.)
Client code can fix this by scaling the source Image before calling drawToBitmapData
, like so:
texImage.scale = (tex.scale / Starling.current.contentScaleFactor);
texImage.drawToBitmapData(renderBitmap);
It's possible that things are working as intended and I simply misunderstood the drawToBitmapData
function. My expectations were that an untransformed Image would produce the same result regardless of contentScaleFactor, and I was surprised that the results were essentially resolution-dependent. But I can certainly understand an argument for the existing behavior as well - it's probably what devs with in-game screenshotting functionality expect, for example.
Mhm, as you already guessed, this is actually working as intended, but I can see why this is a little confusing.
I think the problem is this: you're treating drawToBitmapData
as if it was a method on Image, not DisplayObject. If it were on Image, I could take the image texture's scale
factor into account and create the bitmap data in its optimal size. However, as it's a method on DisplayObject, I don't know anything about a texture. The object could be a Canvas or an untextured Quad, or a Sprite with all different kinds of objects.
Thus, Starling will do the second best thing and use the "dpi" of the stage, if you will. The result is BitmapData that, if converted to a texture, should use Starling's current contentScaleFactor
. Your code would look like this:
// draw the texture
var texImage :Image = new Image(tex);
// render the texture to a bitmap, turn that into a new texture, and draw that
var renderBitmap :BitmapData = texImage.drawToBitmapData();
var renderTex :Texture = Texture.fromBitmapData(renderBitmap, false, false, Starling.contentScaleFactor);
var bitmapImage :Image = new Image(renderTex);
That way, the two images will have the same size & resolution.
Again, you already expected that — I just wanted to describe my reasoning in a little more detail so it's comprehensible. In a nutshell, drawToBitmapData
is a generic method that must work the same for all display objects, and that's why it behaves as it does. :smile:
Hello, I have the same problem, some Textures are partially previewed, cropped or completely invisible. I'm using AssetManager class to get textures from atlas files. So the workaround introduced above doesn't work for me. I don't have access to BitmapData of SubTextures of atlas files.
Is this problem going to get fixed?
I recently rewrote the whole AssetManager architecture, so it would be possible to implement Tim's solution via an extension to that.
However, the problem remains that the final row often is empty/transparent, so I'm not sure yet how best to automate detection. I'll try to think of something! @ll24llll, how reliably can you reproduce the problem? If I sent you some test-code, would you be able to verify if it works?
Dear Daniel, I'll check out the new AssetManager as soon as possible, thanks.
Unfortunately I can not reproduce the problem myself, but some of my clients are sending me screen captures like this every now and then: picture link
By implementing Tim's solution and tracking it, I'll be aware of that how much and in which situations the problem is occurring.
AIR 29 claims to have fixed this issue - have you tried the latest version?
On Wed, Apr 11, 2018 at 11:14 PM, ll24llll notifications@github.com wrote:
Dear Daniel, I'll check out the new AssetManager as soon as possible, thanks.
Unfortunately I can not reproduce the problem myself, but some of my clients are sending me screen captures like this every now and then: picture link http://s9.picofile.com/file/8323517618/tank_texture_bug.jpg
By implementing Tim's solution and tracking it, I'll be aware of that how much and in which situations the problem is occurring.
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Gamua/Starling-Framework/issues/1012#issuecomment-380690592, or mute the thread https://github.com/notifications/unsubscribe-auth/AArRnkbQP8F3MaHI830-pfHKOdrbSzHXks5tnvCtgaJpZM4Q_3HU .
Dear Tim, I implemented AIR29 the day before yesterday and I've not published an update thereafter. I'll let you know if the problem persists.
On Sun, Apr 15, 2018 at 3:00 AM, Tim Conkling notifications@github.com wrote:
AIR 29 claims to have fixed this issue - have you tried the latest version?
On Wed, Apr 11, 2018 at 11:14 PM, ll24llll notifications@github.com wrote:
Dear Daniel, I'll check out the new AssetManager as soon as possible, thanks.
Unfortunately I can not reproduce the problem myself, but some of my clients are sending me screen captures like this every now and then: picture link http://s9.picofile.com/file/8323517618/tank_texture_bug.jpg
By implementing Tim's solution and tracking it, I'll be aware of that how much and in which situations the problem is occurring.
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Gamua/Starling-Framework/issues/ 1012#issuecomment-380690592, or mute the thread https://github.com/notifications/unsubscribe-auth/AArRnkbQP8F3MaHI830- pfHKOdrbSzHXks5tnvCtgaJpZM4Q_3HU .
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Gamua/Starling-Framework/issues/1012#issuecomment-381365133, or mute the thread https://github.com/notifications/unsubscribe-auth/ABUILjTWIawKRIAflpR5apWnyV01jsolks5tonh4gaJpZM4Q_3HU .
The issue has been addressed with AIR 29.
@ll24llll: could you verify if this issue is solved for you? Then I can close this issue.
I haven't seen this problem reported since that mentioned AIR update, so I'm supposing this is fixed! I'll close the issue now, but if anyone reports differently, we can open it up again anytime.
Preamble!
I'm being bitten by a very old AIR-on-Android issue, described in detail on the Starling forum: https://forum.starling-framework.org/topic/textures-partially-uploaded-to-gpu-original-bitmapdata-is-perfect
In short, on Android, textures will occasionally be "cropped" when uploaded to the GPU: all texels below a certain line in the texture will render as invisible. There's no programmer-observable rhyme or reason to when this occurs, and quitting and restarting the game will frequently solve the problem. Here's an example from my game. I've circled the areas subtextures are cropped or completely invisible:
Here's what the screen should look like:
Folks have been reporting this issue way back since AIR 19, and it's still recurring as of AIR 27. Adobe's been notified of it several times. The original Adobe tracker bug was seemingly lost when Adobe updated their bug tracking; a new bug is here: https://tracker.adobe.com/#/view/AIR-4198475
Actually getting to the point!
Adobe seems to have like 1/2 an engineer on AIR these days, so the chances of them fixing this bug are slim. My game is blocked until this is fixed or I can work around it.
Anecdotally, it sounds like there's a potential workaround: after creating a ConcreteTexture on Android, immediately draw that texture to a render-texture and compare the results with the source image that was uploaded to the GPU. If there's a mismatch, recreate the texture.
I'd love for this workaround to be baked into Starling itself (presumably via an opt-in flag that client code can set on Starling during initialization, e.g.
Starling.validateTextureAfterUpload
or something). This would obviously slow texture loading (hence the opt-in), but better a slightly slower load than a broken game.@PrimaryFeather, does this sound reasonable? I'm happy to take a stab at this and make a PR if it seems like something you'd want in Starling. It looks like
ConcretePotTexture.uploadBitmapData
andConcreteRectangleTexture.uploadBitmapData
could check a Starling flag and perform this validation in a loop (with some low number of maximum iterations), delaying anyTextureReady
event until successful?