Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Textures/bitmaps partially loading #25

Closed
FatRedBird opened this issue Oct 3, 2017 · 7 comments
Closed

Textures/bitmaps partially loading #25

FatRedBird opened this issue Oct 3, 2017 · 7 comments

Comments

@FatRedBird
Copy link

Adobe tracker link:
https://tracker.adobe.com/#/view/AIR-4198475

Forum link:
https://forum.starling-framework.org/topic/textures-partially-uploaded-to-gpu-original-bitmapdata-is-perfect/page/2

Problem Description:

On certain android devices (SM-T530 running android 5.0.2), textures created only partially load up, a full horizontal slice from the top of the texture, down to a seemingly arbitrary height, is created, and the bottom is not created (or perhaps uploaded to the gpu). This results in incomplete rendering. This bug did not occur on some other android devices, including the (SM-G935F running android 7.0)

Steps to Reproduce:
Load up a big texture (4096,4096) and attempt to render it on (SM-T530 running android 5.0.2) using air sdk27, it wont render fully, switch to air sdk19 it works, switch back to air sdk27, it doesn't work. Please not that this does not fail on all devices.

Actual Result:
incomplete texture

Expected Result:
corrrect rendering

Any Workarounds:

The bug was allieviated by switching to air sdk 19, however, this is not an option as having previously published with version 27, we are now stuck targetting android:targetSdkVersion="24", which requires persmissions which can only be accessed with air sdk 24.

@FatRedBird
Copy link
Author

Additional info, we have found the error exists in (atleast) air 21,23,24,25,27

The actual error appears appears to be in the bitmap data of the texture object, not the bitmap data that we load from png. If we see a discrepancy between the two assessed by looking for the area of non blank pixels, we know the error has occurred:

		var maskColor:uint = 0xFF000000;
		var emptyColor:uint = 0x00000000;
		var bounds1:Rectangle = bmpdata1.getColorBoundsRect(maskColor, emptyColor, false);	
		var bounds2:Rectangle = bmpdata2.getColorBoundsRect(maskColor, emptyColor, false);	

Where bmpdata 1 and 2 represent the source bitmapdata, and the bitmapdata on the texture object (GTexture in our case as we are using the Genome2d framework). The error can be fixed by recreating the texture with the source bitmap again.

This is obviously not ideal as it's slow and ugly, but maybe it's of help to people.

@tconkling
Copy link

Anecdotally, this seems to happen the first time I launch my game on a new device, or after a restart. Quitting and restarting the game fixes the issue. Uninstalling and reinstalling makes it come back. Restarting the device makes it come back.

@tconkling
Copy link

tconkling commented Nov 27, 2017

@FatRedBird how do you obtain the texture object's BitmapData? Flash's Texture class doesn't seem to expose this. Are you just rendering the original texture to a new BitmapData?

@FatRedBird
Copy link
Author

I'm using the Genom2d framework for my rendering. GTexture has a bitmapdata property.

For a while I also believed it was caused primarilly on first run of a game, however I now beliebe it happens when the app is demanding a lot of RAM. In our first run we tend to have more texture sheets being loaded and disposed due to intro animations.

@PrimaryFeather
Copy link
Contributor

Tim Conkling created a sample project that exhibits this bug on his Nvidia shield. It's available here on Github. He also shot a video of demonstrating the bug using this sample project. It's here on Youtube.

Thanks a lot for your efforts, Tim!

@tconkling
Copy link

tconkling commented Dec 20, 2017

@FatRedBird - I've been spending a lot of time with this bug over the last week, and what I've found is that the issue is actually occurring during bitmap loading, and not during texture creation.

Using the technique you described above, I compare the non-transparent pixel bounds of my loaded bitmaps to the textures I create from them. In all cases, the bounds are the same. However, when the bug is triggered, the non-transparent bounds of the original loaded bitmap differ from what that bitmap's contents are on disk -- it's the bitmaps themselves that are being loaded incorrectly.

The workaround I've just implemented, which accurately detects the issue, tests bitmaps as they're loaded, and reloads them, after a brief timeout, if they're cropped. The major irritation here is that your game needs to know what the bottom-most non-transparent pixel row is for each of your bitmaps. (My game uses texture atlases that are not always completely filled, so the bottom-most non transparent pixel row, for me, is generally not the bottom row of the bitmap.)

Here's a snippet of my validation code, to illustrate what I mean:

public function doNextLoad () :BitmapLoader {
    _numTries++;
    var loader :BitmapLoader = new BitmapLoader(_urlOrByteArray);
    loader.begin().onSuccess(validateBitmap).onFailure(_result.fail);
    return loader;
}

public function validateBitmap (bitmapData :BitmapData) :void {
    var bottomRow :Number = getBottomPixelRow(bitmapData);
    var valid :Boolean = (_expectedBottomRow - bottomRow < 1);

    if (valid || _numTries >= MAX_TRIES) {
        if (!valid) {
            log.warning("Succeeding with broken texture",
                "name", _name, "percentLoaded", (bottomRow / _expectedBottomRow));
        }
        _result.succeed(Texture.fromBitmapData(bitmapData, false, false));
    } else {
        log.warning("Bitmap loaded partially, retrying",
            "name", _name,
            "numTries", _numTries,
            "percentLoaded", (bottomRow / _expectedBottomRow),
            "expectedBottomRow", _expectedBottomRow,
            "loadedBottomRow", bottomRow);

        bitmapData.dispose();

        var timer :Timer = new Timer(RETRY_DELAY * _numTries * 1000.0, 1);
        timer.addEventListener(TimerEvent.TIMER_COMPLETE, function (e :TimerEvent) :void {
            doNextLoad();
        });
        timer.start();
    }
}

public static function getBottomPixelRow (bitmapData :BitmapData) :Number {
    var bounds :Rectangle = bitmapData.transparent ?
        bitmapData.getColorBoundsRect(0xFF000000, 0x0, false) :
        bitmapData.getColorBoundsRect(0xFFFFFFFF, 0x0, false);
    return bounds.bottom;
}

private static const MAX_TRIES :int = 5;
private static const RETRY_DELAY :Number = 0.1;

(This validating bitmaploader has an _expectedBottomRow member variable that it must be initialized with.)

Doing an immediate reload of the offending bitmap often results in the error recurring, so I have a reload timeout that increases with each failure. I haven't yet seen a bitmap fail 5 times in a row, but of course it's possible. The error seems to occur more frequently when there are multiple in-progress bitmap loads -- though this may just mean that the error is more likely when there's more memory being allocated, as you note above.

This code logs a warning when it detects an issue, and anecdotally, the "percentComplete" of failed bitmaps (as measured by the loaded bitmap's bottom-most non-transparent row, divided by its expected bottom-most non-transparent row), has always been 25%, 50%, or 75%, which I didn't expect. My test textures are all 2048x2048. This makes me think that bitmap loading happens in discrete chunks, and there's some early exit that sometimes occurs before all chunks have been loaded.

@PrimaryFeather
Copy link
Contributor

I haven't seen this problem reported since AIR 29, 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants