拥有2d渲染器中级经验的每个人都知道精灵批处理器具有需要更新的图形API特定缓冲区内的数据,我们总是寻找最快的方式来更新它。现在我陷入了两难境地 - 对于Metal和Swift来说,更新最聪明的事情是什么,最聪明的做法是什么?更具体地说,我应该在将顶点发送到GPU之前更新顶点(在CPU上执行顶点和tex坐标转换),或创建变换矩阵,创建tex坐标参数,并将其发送到一个实例化的统一缓冲区中( GPU上的顶点和tex坐标转换)。我这样做的方式目前涉及实例化渲染和一个与8字节对齐的巨型制服缓冲区。
静态数据
static let spritesPerBatch: Int = 1024
static var spritesData: [Float] = [Float](count: spritesPerBatch * BufferConstants.SIZE_OF_SPRITE_INSTANCE_UNIFORMS / sizeof(Float), repeatedValue: 0.0)
排队精灵数据
方法:SpriteBatch.begin()
spritesInBatch = 0
方法:SpriteBatch.submit(sprite)
let offset: Int = spritesInBatch * BufferConstants.SIZE_OF_SPRITE_INSTANCE_UNIFORMS / sizeof(Float)
// transform matrix (3x2)
spritesData[offset + 0] = wsx * cosMetaRot * xOrtho
spritesData[offset + 1] = wsx * sinMetaRot * yOrtho
spritesData[offset + 2] = -hsy * sinMetaRot * xOrtho
spritesData[offset + 3] = hsy * cosMetaRot * yOrtho
spritesData[offset + 4] = (tx * cosNegCameraRotation - ty * sinNegCameraRotation) * xOrtho
spritesData[offset + 5] = (tx * sinNegCameraRotation + ty * cosNegCameraRotation) * yOrtho
// tex coords and lengths
spritesData[offset + 6] = sprite.getU()
spritesData[offset + 7] = sprite.getV()
spritesData[offset + 8] = sprite.getUVW()
spritesData[offset + 9] = sprite.getUVH()
// which texture to use out of the 16 that could be bound
spritesData[offset + 10] = Float(targetTextureIDIndex)
spritesInBatch++
将精灵数据复制到制服缓冲区
方法:SpriteBatch.end()
instancedUniformsBuffer = device.newBufferWithLength(length: spritesPerBatch * BufferConstants.SIZE_OF_SPRITE_INSTANCE_UNIFORMS, options: MTLResourceOptions.CPUCacheModeWriteCombined)
instancedUniformsPointer = instancedUniformsBuffer.contents()
memcpy(instancedUniformsPointer, spritesData, instancedUniformsBuffer.length)
Renderer.renderSpriteBatch()
精灵批量渲染方法
方法:Renderer.renderSpriteBatch()
Shaders.setShaderProgram(Shaders.SPRITE)
let textureIDs: [TextureID] = SpriteBatch.getTextureIDs()
for (var i: Int = 0; i < textureIDs.count; i++) {
renderEncoder.setFragmentTexture(TextureManager.getTexture(textureIDs[i]).texture, atIndex: i)
}
let instancedUniformsBuffer: MTLBuffer = SpriteBatch.getInstancedUniformsBuffer().buffer
renderEncoder.setVertexBuffer(VertexBuffers.SPRITE.buffer, offset: 0, atIndex: 0)
renderEncoder.setVertexBuffer(instancedUniformsBuffer, offset: 0, atIndex: 1)
renderEncoder.drawIndexedPrimitives(MTLPrimitiveType.Triangle, indexCount: BufferConstants.SPRITE_INDEX_COUNT, indexType: MTLIndexType.UInt16, indexBuffer: IndexBuffers.SPRITE.buffer, indexBufferOffset: 0, instanceCount: SpriteBatch.getSpritesInBatch())
我目前能够在iPhone 5s上以60 fps的速度获得大约1400个大小为32x64的精灵和8个单独的纹理。我对此非常满意,并且能用这个数字完成我的iOS游戏。但是,我想推动边界,以便我可以在游戏中使用更好的效果。为了重申这个问题,我还没有说清楚,我想知道两个特定于绩效的重要事项。
感谢和抱歉超长的帖子! :)
答案 0 :(得分:0)
只是一些想法......
我会使用乐器来查看游戏循环中花费最多的时间。然而,'Time Profiler'可能无法帮助你解决GPU方面的问题。
查看XCode中的GPU报告,它应该显示GPU和CPU上每帧的花费时间。如果它已经在16毫秒左右徘徊,那么将更多的工作转移到GPU是毫无意义的。
请注意用memory buffer that is shared across the GPU and CPU替换memcpy
。这样你就可以简单地在Swift中写入数组,并且GPU可以使用它,而无需复制内存。
您可以在Metal计算着色器中重写SpriteBatch.submit(sprite)
,但如果您只执行了几千次,则该方法的计算成本似乎并不昂贵。输出MTLBuffer
将包含可以直接送入渲染编码器的所有spritesData
。您仍然需要将输入数据从CPU获取到GPU(计算)。
你的观点1很有意思。我认为你不想在CPU上转换顶点,但这可能是计算着色器的一个很好的候选者。这类似于我前一段时间做过的boid模拟。金属计算着色器更新每个boid位置和速度,它还创建一个每个boid变换矩阵,然后用于变换构成boids视觉表示的6个顶点位置(用2个三角形绘制的简单鱼)。我的场景是在SceneKit中构建的,因此使用实例化绘制调用实际上并不是一种选择。