☠️

Cubism Coreでモデルを読み込む際の落とし穴

2024/01/07に公開

Live2D Cubism Coreの共有ライブラリをebitengine/puregoで叩いて描画しようと試行錯誤している中でかなりハマったポイントがあるので紹介。

モデルを読み込むにはcsmReviveMocInPlacecsmInitializeModelInPlaceを使うことになっているので、素直に書くと以下のようなコードになる。エラー処理は割愛。

type Core struct {
	csmReviveMocInPlace       func(uintptr, uint) uintptr
	csmGetSizeofModel         func(uintptr) uint
	csmInitializeModelInPlace func(uintptr, uintptr, uint) uintptr
}

type Model struct {
	csmMoc   uintptr
	csmModel uintptr
}

func LoadModel(core *Core) (m Model) {
	moc := "Resources/Haru/Haru.moc3"
	mocBuffer, _ := os.ReadFile(moc)
	m.csmMoc = core.csmReviveMocInPlace(uintptr(unsafe.Pointer(&mocBuffer[0])), uint(len(mocBuffer)))
	size := core.csmGetSizeofModel(m.csmMoc)
	modelBuffer := make([]byte, size)
	m.csmModel = core.csmInitializeModelInPlace(m.csmMoc, uintptr(unsafe.Pointer(&modelBuffer[0])), size)
	return
}

いい感じに見えるし、実行しても処理は成功する。が、その後の関数呼び出しにおいてfound bad pointer in Go heapだのsegmentation violationだの致命的なエラーが発生しまくる。

読み込み自体は成功しているので、問題はその後の処理なのかと思って見当違いの箇所を調べたりしてしまい随分ハマったのだが、結論から言うとmocBuffermodelBufferが破棄されているのが原因だった。

別に破棄されようが関係ないだろうと思っていたが、APIリファレンスをよく読むと

csmReviveMocInPlace、csmInitializeModelInPlaceで⼊⼒に使ったメモリ空間にcsmMoc、
csmModelが存在するので、⼊⼒したメモリ空間は保持しつける必要があります。
また、csmMocは対応するcsmModelすべてが破棄されるまで保持する必要があります。
これはcsmModelがcsmMocを参照しているためです。

と書いてある。たしかによく考えれば当たり前だった...。

なので、破棄されないように構造体のメンバとして保持したりすると解決する。

type Model struct {
	mocBuffer   []byte
	csmMoc      uintptr
	modelBuffer []byte
	csmModel    uintptr
}

func LoadModel(core *Core) (m Model) {
	moc := "Resources/Haru/Haru.moc3"
	m.mocBuffer, _ = os.ReadFile(moc)
	m.csmMoc = csmReviveMocInPlace(uintptr(unsafe.Pointer(&m.mocBuffer[0])), uint(len(m.mocBuffer)))
	size := csmGetSizeofModel(m.csmMoc)
	m.modelBuffer = make([]byte, size)
	m.csmModel = csmInitializeModelInPlace(m.csmMoc, uintptr(unsafe.Pointer(&m.modelBuffer[0])), size)
	return
}

ドキュメントやリファレンスはちゃんと読みましょうってハナシ

Discussion