异步字段生成和 TextureRegion 渲染问题
Async fields generation and TextureRegion rendering problem
我有一段代码可以渲染一个网格,我将从中渲染一个迷宫。
我的构建函数:
fun buildAsync(): Deferred<RegularMaze> {
return KtxAsync.async(newSingleThreadAsyncContext()) {
addEmptyFields()
enableLeftBordersRender()
enableBottomBordersRender()
regularMazeService.convertFieldsToMaze(fields, colsNo, rowsNo)
}
}
它看起来像这样:
但是当我在 async
部分之前移动 addEmptyFields()
时,它呈现正确
还有我的Wall
class
class Wall (width: Float, height: Float, x: Float = 0F, y: Float = 0F, rotation: Float = 0F) : BasePart() {
private val textureRegion: TextureRegion
private val size: Size = Size(width, height)
private val position: Position = Position(x, y, rotation)
var relatedFieldIndex: Int? = null
var shouldBeDraw = true
init {
inject()
val wallTexture = assetsHelper.getTextureFromAsset(TextureAsset.WALL)
wallTexture.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat)
textureRegion = TextureRegion(wallTexture, 0, 0, size.widthInt, size.heightInt)
setBounds(
position.x,
position.y,
width,
height
)
rotateBy(position.rotation)
}
override fun draw(batch: Batch, parentAlpha: Float) {
super.draw(batch, parentAlpha)
if (shouldBeDraw) {
batch.draw(textureRegion, position.x, position.y, 0F, 0F, size.width, size.height, 1F, 1F, position.rotation)
}
}
class Size (val width: Float, val height: Float) {
val widthInt: Int
get() = ceil(width).toInt()
val heightInt: Int
get() = ceil(height).toInt()
}
data class Position(val x: Float, val y: Float, val rotation: Float)
}
[EDIT/UPDATE]
我发现了一些奇怪的事情,当我在 async
之前创建一个没有尺寸的 Wall
它开始工作时。 "Working"代码:
fun buildAsync(): Deferred<RegularMaze> {
Wall(0f,0f) // <---- new line
return KtxAsync.async(newSingleThreadAsyncContext()) {
addEmptyFields()
enableLeftBordersRender()
enableBottomBordersRender()
regularMazeService.convertFieldsToMaze(fields, colsNo, rowsNo)
}
}
为什么?
您 运行 陷入并发问题。 "fixed" 代码可能由于在执行协程或在主线程上执行某些关键操作时出现轻微延迟而意外运行,而不是因为它解决了实际问题。
根据经验,永远不要修改不同线程池上的渲染线程访问的任何内容。我的猜测是,在执行协程时某些东西修改或访问了 regularMazeService
,或者 assetsHelper.getTextureFromAsset(TextureAsset.WALL)
无法在后台线程中加载纹理,因为它缺少 OpenGL 上下文。
如果您想在单独的线程上执行某些操作的昂贵部分 - 没关系,但任何修改渲染线程状态的操作都应该在 on 渲染线程。例如:
fun buildAsync(): Deferred<RegularMaze> {
return KtxAsync.async(asyncContext) {
onRenderingThread {
addEmptyFields()
}
enableLeftBordersRender()
enableBottomBordersRender()
regularMazeService.convertFieldsToMaze(fields, colsNo, rowsNo)
}
}
如果您的代码的任何部分是通过使用自定义线程启动的协程执行的:
- 需要访问 OpenGL 上下文(例如纹理加载)。
- 从呈现线程访问或修改数据。
- 根据渲染增量时间执行任何类型的渲染或更新。
你应该把它移到 onRenderingThread
块中。
此外,您应该重用协程上下文。不要使用 newSingleThreadAsyncContext()
为每个协程启动新线程 - 分配 AsyncContext
并重用其实例。
如果您手动加载纹理,我鼓励您使用 ktx-assets-async
模块中可用的 AssetStorage
。它利用协同程序实现真正的并行资产加载。
我有一段代码可以渲染一个网格,我将从中渲染一个迷宫。 我的构建函数:
fun buildAsync(): Deferred<RegularMaze> {
return KtxAsync.async(newSingleThreadAsyncContext()) {
addEmptyFields()
enableLeftBordersRender()
enableBottomBordersRender()
regularMazeService.convertFieldsToMaze(fields, colsNo, rowsNo)
}
}
它看起来像这样:
但是当我在 async
部分之前移动 addEmptyFields()
时,它呈现正确
还有我的Wall
class
class Wall (width: Float, height: Float, x: Float = 0F, y: Float = 0F, rotation: Float = 0F) : BasePart() {
private val textureRegion: TextureRegion
private val size: Size = Size(width, height)
private val position: Position = Position(x, y, rotation)
var relatedFieldIndex: Int? = null
var shouldBeDraw = true
init {
inject()
val wallTexture = assetsHelper.getTextureFromAsset(TextureAsset.WALL)
wallTexture.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat)
textureRegion = TextureRegion(wallTexture, 0, 0, size.widthInt, size.heightInt)
setBounds(
position.x,
position.y,
width,
height
)
rotateBy(position.rotation)
}
override fun draw(batch: Batch, parentAlpha: Float) {
super.draw(batch, parentAlpha)
if (shouldBeDraw) {
batch.draw(textureRegion, position.x, position.y, 0F, 0F, size.width, size.height, 1F, 1F, position.rotation)
}
}
class Size (val width: Float, val height: Float) {
val widthInt: Int
get() = ceil(width).toInt()
val heightInt: Int
get() = ceil(height).toInt()
}
data class Position(val x: Float, val y: Float, val rotation: Float)
}
[EDIT/UPDATE]
我发现了一些奇怪的事情,当我在 async
之前创建一个没有尺寸的 Wall
它开始工作时。 "Working"代码:
fun buildAsync(): Deferred<RegularMaze> {
Wall(0f,0f) // <---- new line
return KtxAsync.async(newSingleThreadAsyncContext()) {
addEmptyFields()
enableLeftBordersRender()
enableBottomBordersRender()
regularMazeService.convertFieldsToMaze(fields, colsNo, rowsNo)
}
}
为什么?
您 运行 陷入并发问题。 "fixed" 代码可能由于在执行协程或在主线程上执行某些关键操作时出现轻微延迟而意外运行,而不是因为它解决了实际问题。
根据经验,永远不要修改不同线程池上的渲染线程访问的任何内容。我的猜测是,在执行协程时某些东西修改或访问了 regularMazeService
,或者 assetsHelper.getTextureFromAsset(TextureAsset.WALL)
无法在后台线程中加载纹理,因为它缺少 OpenGL 上下文。
如果您想在单独的线程上执行某些操作的昂贵部分 - 没关系,但任何修改渲染线程状态的操作都应该在 on 渲染线程。例如:
fun buildAsync(): Deferred<RegularMaze> {
return KtxAsync.async(asyncContext) {
onRenderingThread {
addEmptyFields()
}
enableLeftBordersRender()
enableBottomBordersRender()
regularMazeService.convertFieldsToMaze(fields, colsNo, rowsNo)
}
}
如果您的代码的任何部分是通过使用自定义线程启动的协程执行的:
- 需要访问 OpenGL 上下文(例如纹理加载)。
- 从呈现线程访问或修改数据。
- 根据渲染增量时间执行任何类型的渲染或更新。
你应该把它移到 onRenderingThread
块中。
此外,您应该重用协程上下文。不要使用 newSingleThreadAsyncContext()
为每个协程启动新线程 - 分配 AsyncContext
并重用其实例。
如果您手动加载纹理,我鼓励您使用 ktx-assets-async
模块中可用的 AssetStorage
。它利用协同程序实现真正的并行资产加载。