SpriteKit のテクスチャ キャッシング
「簡単に言えば、Sprite Kit を頼りに正しいことをしてください。」</a> -@LearnCocos2D
iOS 9の時点での長い話は次のとおりです。
SKTexture
テクスチャのかさばる画像データは、オブジェクトに直接保持されません。(SKTexture
クラス リファレンスを参照してください。)
SKTexture
必要になるまでデータのロードを延期します。大きなイメージ ファイルからでも、テクスチャの作成は高速で、メモリの消費もほとんどありません。
テクスチャのデータは、通常、対応するスプライト ノードが作成されるときにディスクからロードされます。(または実際には、size
メソッドが呼び出されたときなど、データが必要なときはいつでも)。
テクスチャのデータは、(最初の) レンダー パス中にレンダリング用に準備されます。
SpriteKit には、テクスチャのかさばる画像データ用のキャッシュが組み込まれています。2 つの機能:
テクスチャのかさばる画像データは、SpriteKit がそれを取り除きたいと感じるまで、SpriteKit によってキャッシュされます。
クラス リファレンスによると、SKTexture
「SKTexture オブジェクトのレンダリングの準備が整うと、テクスチャ オブジェクトへのすべての強い参照が削除されるまで準備が整います。」</p>
現在の iOS では、おそらくシーン全体がなくなった後でも、長く残る傾向があります。 StackOverflow のコメントは、Apple のテクニカル サポートの言葉を引用しています。
シミュレーターでテスト プロジェクトを実行すると、removeFromParent
. ただし、物理デバイスで実行すると、メモリが残っているように見えました。テクスチャのレンダリングと割り当て解除を繰り返しても、追加のディスク アクセスは発生しませんでした。
私は疑問に思います: メモリが重要な状況 (テクスチャは保持されているが現在表示されていない場合) で、レンダリング メモリを早期に解放できますか?
SpriteKit は、キャッシュされたかさばる画像データをスマートに再利用します。
私の実験では、それを再利用しないことは困難でした。
SKTexture
スプライト ノードに表示されているテクスチャがあるとしますが、オブジェクトを再利用するのではなく[SKTexture
textureWithImageNamed:]
、同じ画像名を呼び出します。テクスチャは元のテクスチャと同じポインタではありませんが、かさばる画像データを共有します。
上記は、画像ファイルがアトラスの一部であるかどうかにかかわらず当てはまります。
上記は、元のテクスチャが を使用してロードされたか、 を
[SKTexture textureWithImageNamed:]
使用してロードされたかに関係なく当てはまります[SKTextureAtlas
textureNamed:]
。
別の例: を使用してテクスチャ アトラス オブジェクトを作成するとします[SKTextureAtlas atlasNamed:]
。を使用してそのテクスチャの 1 つを取得textureNamed:
し、アトラスを保持しません。SKTexture
テクスチャをスプライト ノードに表示します (そのため、テクスチャはアプリで強力に保持されます) が、キャッシュ内で特定のものを追跡する必要はありません。次に、新しいテクスチャ アトラス、新しいテクスチャ、新しいノードなど、すべてをもう一度やり直します。これらのオブジェクトはすべて新しく割り当てられますが、比較的軽量です。一方、重要なこととして、最初に読み込まれたかさばる画像データは、インスタンス間で透過的に共有されます。
これを試してください: モンスター アトラスを名前でロードし、そのオーク テクスチャの 1 つを取得してオーク スプライト ノードでレンダリングします。その後、プレイヤーはホーム画面に戻ります。アプリケーションの状態を保存するときに orc ノードをエンコードし、アプリケーションの状態を復元するときにデコードします。(エンコードするとき、バイナリ データはエンコードしません。代わりにその名前をエンコードします。) 復元されたアプリで、別の orc を作成します (新しいアトラス、テクスチャ、およびノードを使用)。この新しいオークは、かさばるオーク データをデコードされたオークと共有しますか? はい。はい、そうします。
テクスチャ イメージ データを再利用しないようにテクスチャを取得する唯一の方法は、を使用してテクスチャを初期化すること[SKTexture
textureWithImage:]
です。確かにUIImage
、画像ファイルの独自の内部キャッシュを実行する可能性がありますが、いずれにせよSKTexture
、データを担当し、レンダリング データを他の場所で再利用することはありません。
要するに、ゲームで同じスプライトが 2 つ同時に表示されている場合、それらがメモリを効率的に使用している可能性は十分にあります。
これら 2 つのポイントをまとめると、SpriteKit には組み込みのキャッシュがあり、重要でかさばる画像データを保持し、スマートに再利用します。
言い換えれば、それだけで機能します。
約束はできません。テスト アプリを実行しているシミュレーターでは、SpriteKit がキャッシュからテクスチャ データを実際に削除する前に削除していることを簡単に証明できます。
ただし、プロトタイピング中に、単一のアトラスやテクスチャを再利用したことがなくても、アプリのかなり良い動作を見つけて驚くかもしれません。
SpriteKitにはアトラスキャッシングもあります
SpriteKit には、テクスチャ アトラス専用のキャッシュ メカニズムがあります。それはこのように動作します:
[SKTextureAtlas atlasNamed:]
テクスチャ アトラスをロードするために呼び出します。(前に述べたように、これはまだかさばる画像データをロードしていません。)
アプリのどこかにアトラスを強く保持します。
後で[SKTextureAtlas atlasNamed:]
同じアトラス名で呼び出すと、返されるオブジェクトは、保持されているアトラスと同じポインターになります。を使用してアトラスから抽出されたテクスチャ
textureNamed:
も、ポインタと同一になります。(更新: iOS10 では、テクスチャは必ずしもポインターと同じではありません。)
テクスチャ オブジェクトは、アトラスを保持しません。
独自のキャッシュを構築したい場合があります
とにかく、独自のキャッシングと再利用メカニズムを構築しているようです。なぜあなたはそれをしているのですか?
最終的には、特定のテクスチャをいつ保持または消去するかについて、より良い情報を得ることができます。
ロードのタイミングを完全に制御する必要がある場合があります。たとえば、テクスチャを最初にレンダリングしたときに即座に表示する場合は、 と のメソッドを使用し
preload
ます。その場合、プリロードされたテクスチャまたはアトラスへの参照を保持する必要がありますよね? それとも、SpriteKit は関係なくそれらをキャッシュしますか? 不明。カスタム アトラスまたはテクスチャ キャッシュは、完全な制御を維持するための優れた方法です。SKTexture
SKTextureAtlas
最適化の特定の時点で (早すぎることは禁じられています!! )、どんなに軽量であっても、新しいオブジェクトSKTexture
や
オブジェクトを何度も作成するのをやめるのが理にかなっています。SKTextureAtlas
アトラスはそれほど軽量ではないため、最初にアトラス再利用メカニズムを構築することになるでしょう (結局のところ、アトラスにはテクスチャのディクショナリがあります)。後で、アトラス以外のオブジェクトを再利用するために別のテクスチャ キャッシュ メカニズムを構築する場合がありSKTexture
ます。あるいは、その 2 番目のものにたどり着かないかもしれません。結局のところ、あなたは忙しいのに、キッチンは自分で掃除していません。
そうは言っても、キャッシングと再利用の動作は、おそらく SpriteKit のものと不気味に似たものになるでしょう。
SpriteKit のテクスチャ キャッシュは、独自のテクスチャ キャッシュ設計にどのように影響しますか? 留意すべき事項は次のとおりです (上から)。
名前付きテクスチャを使用する場合、かさばる画像データのリリースのタイミングを直接制御することはできません。参照を解放すると、必要に応じて SpriteKit がメモリを解放します。
メソッドを使用して、かさばる画像データの読み込みのタイミングを制御できますpreload
。
SpriteKit の内部キャッシングに依存している場合、アトラス キャッシュはオブジェクトへの参照を保持SKTextureAtlas
するだけで、オブジェクトを返す必要はありません。アトラス オブジェクトは、アプリ全体で自動的に再利用されます。
同様に、テクスチャ キャッシュはオブジェクトへの参照を保持するだけで、それらを返す必要はありません。SKTexture
かさばる画像データは、アプリ全体で自動的に再利用されます。(ただし、これは少し奇妙です。適切な動作を確認するのは面倒です。)
最後の 2 つのポイントを考慮して、シングルトン キャッシュ オブジェクトに代わる設計を検討してください。代わりに、スプライト オブジェクトまたはそのコントローラーで使用中のアトラスを保持できます。コントローラーの有効期間中、アプリ内の呼び出しはすべてatlasNamed:
、ポインターと同一のアトラスを再利用します。
はい、 2 つのポインターが同一SKTexture
のオブジェクトは同じメモリを共有しますが、SpriteKit のキャッシュにより、その逆は必ずしも真ではありません。メモリの問題をデバッグSKTexture
していて、ポインタが同一であると予想していた 2 つのオブジェクトが見つかったが、そうでない場合、それらは依然としてかさばる画像データを共有している可能性があります。
テスト
私はツール初心者なので、Allocations インストゥルメントを使用して、リリース ビルドで全体的なアプリのメモリ使用量を測定しました。
「All Heap & Anonymous VM」は、連続実行時に 2 つの安定した値を交互に使用することがわかりました。各テストを数回実行し、結果として最も低いメモリ値を使用しました。
私のテストでは、それぞれ 2 つの画像を持つ 2 つの異なるアトラスがあります。アトラス A と B、イメージ 1 と 2 を呼び出します。ソース イメージは大きいです (760 KiB が 1 つ、950 KiB が 1 つ)。
アトラスは を使用してロードされ[SKTextureAtlas atlasNamed:]
ます。テクスチャは を使用してロードされ[SKTexture textureWithImageNamed:]
ます。下の表で、loadは実際には「スプライト ノードを配置してレンダリングする」ことを意味します。</p>
All Heap
& Anon VM
(MiB) Test
--------- ------------------------------------------------------
106.67 baseline
106.67 preload atlases but no nodes
110.81 load A1
110.81 load A1 and reuse in different two sprite nodes
110.81 load A1 with retained atlas
110.81 load A1,A1
110.81 load A1,A1 with retained atlas
110.81 load A1,A2
110.81 load A1,A2 with retained atlas
110.81 load A1 two different ways*
110.81 load A1 two different ways* with retained atlas
110.81 load A1 or A2 randomly on each tap
110.81 load A1 or A2 randomly on each tap with retained atlas
114.87 load A1,B1
114.87 load A1,A2,B1,B2
114.87 load A1,A2,B1,B2 with preload atlases
* Load A1 two different ways: Once using [SKTexture
textureWithImageNamed:] and once using [SKTextureAtlas
textureNamed:].
内部構造
調査中に、SpriteKit のテクスチャ オブジェクトとアトラス オブジェクトの内部構造に関するいくつかの真の事実を発見しました。
面白い?それはあなたがどのようなことに興味を持っているかによります!
からのテクスチャの構造SKTextureAtlas
アトラスが によって読み込まれる[SKTextureAtlas atlasNamed:]
と、実行時にそのテクスチャを検査すると、いくらかの再利用が示されます。
Xcode のビルド中に、スクリプトは個々の画像ファイルからアトラスをコンパイルして、多数の大きなスプライト シート画像 (サイズによって制限され、@1x @2x @3x の解像度でグループ化されます) にコンパイルします。アトラスの各テクスチャは、バンドル パスによってスプライト シート イメージを参照し、
_imgName
( _isPath
true に設定) に格納されます。
アトラスの各テクスチャは、その によって個別に識別され
、スプライト シート_subTextureName
にtextureRect
挿入されます。
同じスプライト シート イメージを共有するアトラス内のすべてのテクスチャは、同一の非 nil ivar_originalTexture
と
_textureCache
.
shared_originalTexture
は、それ自体がSKTexture
オブジェクトであり、おそらくスプライト シート イメージ全体を表します。_subTextureName
独自のものtextureRect
はなく
、 (0, 0, 1,
1)
.
アトラスがメモリから解放されてから再ロードされると、新しいコピーには異なるSKTexture
オブジェクト、異なる_originalTexture
オブジェクト、および異なる_textureCache
オブジェクトが含まれます。私が見る限り、_imgName
(つまり、実際の画像ファイル) だけが新しいアトラスを古いアトラスに接続します。
からではないテクスチャの構造SKTextureAtlas
を使用してテクスチャをロードする[SKTexture textureWithImageNamed:]
と、アトラスから取得された可能性がありますが、
SKTextureAtlas
.
この方法でロードされたテクスチャには、上記との違いがあります。
_imgName
「giraffe.png」のような短い があり、 _isPath
false に設定されています。
unset があり_originalTexture
ます。
それは(どうやら)独自のものを持ってい_textureCache
ます。
(同じイメージ名で)によって読み込まれた2 つSKTexture
のオブジェクトは、textureWithImageNamed:
_imgName.
それにもかかわらず、上記で徹底的に説明したように、この種のテクスチャ構成は、かさばる画像データを他の種類のテクスチャ構成と共有します。これは、キャッシュが実際の画像ファイルの近くで行われることを意味します。