0

我一直致力于创建一个可以TextureAtlas在需要时生成动态对象的资产类。具体方法是Assets.generateTextureAtlas(),我正在尝试尽可能优化它,因为我经常需要重新生成纹理图集,并希望获得比我平均 53 毫秒更好的时间。

53ms 目前花费了我大约 3 帧,这可以快速增加我需要在我的纹理图集中打包的更多项目以及我需要生成它们的频率。因此,我的代码中所有陷阱的答案会很棒。

整个类代码可在 github gist 中找到。

该类RectanglePacker仅用于将矩形尽可能紧密地打包在一起(类似于 Texture Packer),可以在此处找到。

供参考,方法如下:

public static function generateTextureAtlas(folder:String):void
{
    if (!_initialised) throw new Error("Assets class not initialised.");

    if (_renderTextureAtlases[folder] != null)
    {
        (_renderTextureAtlases[folder] as TextureAtlas).dispose();
    }

    var i:int;
    var image:Image = new Image(_blankTexture);
    var itemName:String;
    var itemNames:Vector.<String> = Assets.getNames(folder + "/");
    var itemsTexture:RenderTexture;
    var itemTexture:Texture;
    var itemTextures:Vector.<Texture> = Assets.getTextures(folder + "/");
    var noOfRectangles:int;
    var rect:Rectangle;
    var rectanglePacker:RectanglePacker = new RectanglePacker();
    var texture:Texture;

    noOfRectangles = itemTextures.length;

    if (noOfRectangles == 0)
    {
        return;
    }

    for (i = 0; i < noOfRectangles; i++)
    {
        rectanglePacker.insertRectangle(Math.round(itemTextures[i].width), Math.round(itemTextures[i].height), i);
    }

    rectanglePacker.packRectangles();

    if (rectanglePacker.rectangleCount != noOfRectangles)
    {
        throw new Error("Only " + rectanglePacker.rectangleCount + " out of " + noOfRectangles + " rectangles packed for folder: " + folder);
    }

    itemsTexture = new RenderTexture(rectanglePacker.width, rectanglePacker.height);

    itemsTexture.drawBundled(function():void
    {
        for (i = 0; i < noOfRectangles; i++)
        {
            itemTexture = itemTextures[rectanglePacker.getRectangleId(i)];
            rect = rectanglePacker.getRectangle(i, rect);

            image.texture = itemTexture;
            image.readjustSize();

            image.x = rect.x + itemTexture.frame.x;
            image.y = rect.y + itemTexture.frame.y;

            itemsTexture.draw(image);
        }
    });

    _renderTextureAtlases[folder] = new TextureAtlas(itemsTexture);

    for (i = 0; i < noOfRectangles; i++)
    {
        itemName = itemNames[rectanglePacker.getRectangleId(i)];
        itemTexture = itemTextures[rectanglePacker.getRectangleId(i)];
        rect = rectanglePacker.getRectangle(i);

        (_renderTextureAtlases[folder] as TextureAtlas).addRegion(itemName, rect, itemTexture.frame);
    }
}
4

3 回答 3

0

仔细阅读项目并找到所有可以优化的东西肯定需要时间。

首先删除对rectanglePacker.getRectangle(i)内部循环的多个调用。

例如 :

    itemName = itemNames[rectanglePacker.getRectangleId(i)];
    itemTexture = itemTextures[rectanglePacker.getRectangleId(i)];
    rect = rectanglePacker.getRectangle(i);

也许,可能是:

    rect = rectanglePacker.getRectangle(i);
    itemName = itemNames[rect];
    itemTexture = itemTextures[rect];

如果getRectangle确实只是“得到一个矩形”而不设置任何东西。

于 2013-01-26T06:15:55.187 回答
0

As others said, it's unlikely that the reason of bad performance is non-optimized AS code. Output from the profiler (Scout, for example) wold be very helpful. However, if your purpose is just adding new textures, I can suggest several optimizations:

  1. Why would you need to re-generate the whole atlas every time (calling Assets.getTextures() and creating new render texture)? Why don't you just add new items to the existing atlas? Creation of a new RenderTexture (and, thus, a new texture in GPU memory) is very costly operation, because it requires sync between CPU and GPU. On the other hand, drawing into RenderTexture is carried out entirely inside GPU, so it takes much less time.

  2. If you place every item on a grid, then you can avoid using RectanglePacker as all of your rectangles can have the same dimensions matching the dimensions of a grid.

Edit:

To clarify, some time ago I had a similar problem: I had to add new items to the existing atlas on a regular basis. And the performance of this operation was quite acceptable (about 8ms on iPad3 using 1024x1024 dynamic texture). But I used the same RenderTexture and the same Sprite object that contained my dynamic atlas items. When I need to add a new item, I just create new Image with desired texture (stand-alone or from another static atlas), then place it inside the Sprite container, and then redraw this container to the RenderTexture. Similarly with deletion/modification of an item.

于 2013-01-26T13:25:32.923 回答
0

我认为手头的更大问题是,为什么你必须在运行时这样做,在这种情况下不能花费更多时间?这是一个扩展操作,无论您如何优化它,在 AS3 中完成时您可能最终会花费大约 40 毫秒或类似的时间。

这就是为什么这些操作应该在编译期间或在“加载屏幕”或其他“转换”期间完成,此时帧速率并不重要并且您可以负担得起。

或者,用 c++ 或其他语言创建另一个系统,该系统实际上可以处理为您提供最终结果的数字运算。

此外,在检查性能时,是的,整个函数需要 53 毫秒,但是,这些毫秒在哪里使用?53ms 什么也没说,只是你发现罪魁祸首的“开销分析”,你需要将它分解成更小的块,以收集一些关于在该函数内部实际需要时间的可靠信息。

我的意思是,在该函数内部,您有 3 个 for 循环、对其他类的多次调用、强制转换、删除、创建。这不像你在做一件事,那个函数可能会导致大约 500 行代码和无数的 cpu 操作。而且,您不知道它在哪里使用。我猜这rectanglePacker.packRectangles();需要 60% 的时间,但是如果没有分析,您和我们都不知道要优化什么,我们根本没有足够的数据。

如果您必须在 AS3 的运行时执行此操作,我建议您将其分散在几个帧中,并在 10 帧左右平均分配工作负载。您也可以在另一个线程和工作人员的帮助下完成它。但最重要的是,这似乎是一个设计错误,因为这可能会在其他时间完成。如果不是,那么使用另一种更擅长此类操作的语言。

对此进行分析的最简单方法是添加几个类似于以下内容的时间戳:

var timestamps:Array = [];

然后在代码中的不同位置推送getTimer(),然后在函数完成时将它们打印出来

于 2013-01-26T08:39:28.920 回答