Skip to content

Commit

Permalink
added webgpu timestamp queries
Browse files Browse the repository at this point in the history
  • Loading branch information
fabmax committed Mar 2, 2024
1 parent 139b21d commit 1b32d1a
Show file tree
Hide file tree
Showing 15 changed files with 277 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ interface RenderBackend {
val isInvertedNdcY: Boolean get() = deviceCoordinates.ndcYDirection == NdcYDirection.TOP_TO_BOTTOM
val hasComputeShaders: Boolean

val frameGpuTime: Double

fun renderFrame(ctx: KoolContext)
fun cleanup(ctx: KoolContext)

Expand Down
43 changes: 32 additions & 11 deletions kool-core/src/commonMain/kotlin/de/fabmax/kool/util/DebugOverlay.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class DebugOverlay(position: Position = Position.UPPER_RIGHT) {
val ui: Scene

private val fpsText = mutableStateOf("")
private val frameTimeText = mutableStateOf("")
private val sysInfos = mutableStateListOf<String>()
private val viewportText = mutableStateOf("")
private val uptimeText = mutableStateOf("")
Expand All @@ -49,6 +50,12 @@ class DebugOverlay(position: Position = Position.UPPER_RIGHT) {

ui = UiScene("debug-overlay") {
onUpdate += {

val frameTime = it.ctx.backend.frameGpuTime
if (frameTime != 0.0) {
frameTimeText.set("${frameTime.toString(2)} ms @ GPU")
}

fpsText.set("${it.ctx.fps.toString(1)} fps")
if (isExpanded.value) {
updateExpandedStats(it)
Expand All @@ -70,23 +77,37 @@ class DebugOverlay(position: Position = Position.UPPER_RIGHT) {
// min width
Box { modifier.width(180.dp) }

Text(fpsText.use()) {
Column(Grow.Std) {
modifier
.alignX(AlignmentX.Center)
.padding(sizes.gap)
.width(Grow.Std)
.textAlignX(AlignmentX.Center)
.font(fpsFont)
.textColor(colors.primary)
.background(deltaTGraph)
.padding(sizes.gap)

Text(if (isExpanded.use()) "-" else "+") {
Text(fpsText.use()) {
modifier
.align(AlignmentX.End, AlignmentY.Center)
//.padding(sizes.gap)
.width(Grow.Std)
.textAlignX(AlignmentX.Center)
.font(fpsFont)
.textColor(colors.primary)
.onClick { isExpanded.set(!isExpanded.value) }
.margin(end = sizes.gap)

Text(if (isExpanded.use()) "-" else "+") {
modifier
.align(AlignmentX.End, AlignmentY.Center)
.font(fpsFont)
.textColor(colors.primary)
.onClick { isExpanded.set(!isExpanded.value) }
.margin(end = sizes.gap)
}
}

val frameTimeTxt = frameTimeText.use()
if (frameTimeTxt.isNotEmpty()) {
Text(frameTimeTxt) {
modifier
.alignX(AlignmentX.Center)
.margin(top = sizes.smallGap)
.textColor(colors.primary)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ class RenderBackendGlImpl(ctx: KoolContext) :
override val glfwWindow: GlfwWindow
override val glslGeneratorHints: GlslGenerator.Hints

private val timer: TimeQuery
override var frameGpuTime: Double = 0.0

init {
glfwWindow = createWindow()

Expand All @@ -36,10 +39,18 @@ class RenderBackendGlImpl(ctx: KoolContext) :
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS)
setupGl()

timer = TimeQuery(gl)
}

override fun renderFrame(ctx: KoolContext) {
super.renderFrame(ctx)
if (timer.isAvailable) {
frameGpuTime = timer.getQueryResultMillis()
}

timer.timedScope {
super.renderFrame(ctx)
}
glfwSwapBuffers(glfwWindow.windowPtr)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class VkRenderBackend(val ctx: Lwjgl3Context) : RenderBackendJvm {
private val semaPool: SemaphorePool
private val renderPassGraph = RenderPassGraph()

override var frameGpuTime: Double = 0.0

init {
val vkSetup = VkSetup().apply {
isValidating = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class MockBackend(val shaderGen: KslGenerator = GlslGenerator(GlslGenerator.Hint
override val deviceCoordinates: DeviceCoordinates = DeviceCoordinates.OPEN_GL
override val hasComputeShaders: Boolean = false

override var frameGpuTime: Double = 0.0

override fun renderFrame(ctx: KoolContext) { }

override fun cleanup(ctx: KoolContext) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class RenderBackendGlImpl(ctx: KoolContext, canvas: HTMLCanvasElement) :
glslVersionStr = "#version 300 es",
)

override var frameGpuTime: Double = 0.0

init {
val options = js("({})")
options["powerPreference"] = KoolSystem.configJs.powerPreference
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ class RenderPassEncoderState<T: RenderPass>(val gpuRenderPass: WgpuRenderPass<T>
_renderPass = renderPass
}

fun begin(viewIndex: Int, mipLevel: Int, forceLoad: Boolean = false) {
fun begin(viewIndex: Int, mipLevel: Int, timestampWrites: GPURenderPassTimestampWrites? = null, forceLoad: Boolean = false) {
check(!isPassActive)

val (colorAttachments, depthAttachment) = gpuRenderPass.getRenderAttachments(renderPass, viewIndex, mipLevel, forceLoad)
_passEncoder = encoder.beginRenderPass(colorAttachments, depthAttachment, renderPass.name)
_passEncoder = encoder.beginRenderPass(colorAttachments, depthAttachment, timestampWrites, renderPass.name)
isPassActive = true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,20 @@ class RenderBackendWebGpu(val ctx: KoolContext, val canvas: HTMLCanvasElement) :
internal lateinit var textureLoader: WgpuTextureLoader
private set

internal val timestampQuery: WgpuTimestamps by lazy {
WgpuTimestamps(128, this)
}

val pipelineManager = WgpuPipelineManager(this)
private val sceneRenderer = WgpuScreenRenderPass(this)

private var renderSize = Vec2i(canvas.width, canvas.height)

private val gpuReadbacks = mutableListOf<GpuReadback>()

// right now, we can only query individual render pass times, not the entire frame time
override val frameGpuTime: Double = 0.0

init {
check(isSupported()) {
val txt = "WebGPU not supported on this browser."
Expand All @@ -64,7 +71,19 @@ class RenderBackendWebGpu(val ctx: KoolContext, val canvas: HTMLCanvasElement) :
txt
}

device = adapter.requestDevice().await()
val availableFeatures = mutableSetOf<String>()
adapter.features.forEach { s: String -> availableFeatures.add(s) }
logD { "Available GPUAdapter features:" }
availableFeatures.forEach { logD { it } }

val requiredFeatures = mutableListOf<String>()
if ("timestamp-query" in availableFeatures) {
logI { "Enabling WebGPU timestamp-query feature" }
requiredFeatures.add("timestamp-query")
}

val deviceDesc = GPUDeviceDescriptor(requiredFeatures.toTypedArray())
device = adapter.requestDevice(deviceDesc).await()

canvasContext = canvas.getContext("webgpu") as GPUCanvasContext
_canvasFormat = navigator.gpu.getPreferredCanvasFormat()
Expand Down Expand Up @@ -101,7 +120,10 @@ class RenderBackendWebGpu(val ctx: KoolContext, val canvas: HTMLCanvasElement) :
// copy all buffers requested for readback to temporary buffers using the current command encoder
copyReadbacks(encoder)
}
timestampQuery.resolve(encoder)
device.queue.submit(arrayOf(encoder.finish()))

timestampQuery.readTimestamps()
if (gpuReadbacks.isNotEmpty()) {
// after encoder is finished and submitted, temp buffers can be mapped for readback
mapReadbacks()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import de.fabmax.kool.pipeline.ComputePassImpl
import de.fabmax.kool.pipeline.ComputeRenderPass
import de.fabmax.kool.util.BaseReleasable
import de.fabmax.kool.util.logE
import de.fabmax.kool.util.releaseWith

class WgpuComputePass(val parentPass: ComputeRenderPass, val backend: RenderBackendWebGpu) :
BaseReleasable(),
ComputePassImpl
{
private val computePassEncoderState = ComputePassEncoderState()

private var beginTimestamp: WgpuTimestamps.QuerySlot? = null
private var endTimestamp: WgpuTimestamps.QuerySlot? = null

fun dispatch(encoder: GPUCommandEncoder) {
val tasks = parentPass.tasks

Expand All @@ -20,7 +24,19 @@ class WgpuComputePass(val parentPass: ComputeRenderPass, val backend: RenderBack
val maxWorkGroupSzZ = backend.device.limits.maxComputeWorkgroupSizeZ
val maxInvocations = backend.device.limits.maxComputeInvocationsPerWorkgroup

computePassEncoderState.setup(encoder, encoder.beginComputePass())
var timestampWrites: GPUComputePassTimestampWrites? = null
if (parentPass.isProfileTimes) {
createTimestampQueries()
val begin = beginTimestamp
val end = endTimestamp
if (begin != null && end != null && begin.isReady && end.isReady) {
parentPass.tGpu = (end.latestResult - begin.latestResult) / 1e6
timestampWrites = GPUComputePassTimestampWrites(backend.timestampQuery.querySet, begin.index, end.index)
}
}
val desc = GPUComputePassDescriptor(parentPass.name, timestampWrites)

computePassEncoderState.setup(encoder, encoder.beginComputePass(desc))
for (i in tasks.indices) {
val task = tasks[i]
if (task.isEnabled) {
Expand Down Expand Up @@ -54,4 +70,13 @@ class WgpuComputePass(val parentPass: ComputeRenderPass, val backend: RenderBack
}
computePassEncoderState.end()
}

private fun createTimestampQueries() {
if (beginTimestamp == null) {
beginTimestamp = backend.timestampQuery.createQuery()?.also { it.releaseWith(this) }
}
if (endTimestamp == null) {
endTimestamp = backend.timestampQuery.createQuery()?.also { it.releaseWith(this) }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,10 @@ interface GPUComputePassDescriptor

fun GPUComputePassDescriptor(
label: String = "",
//timestampWrites: GPUComputePassTimestampWrites
timestampWrites: GPUComputePassTimestampWrites? = null
): GPUComputePassDescriptor {
val o = js("({})")
timestampWrites?.let { o["timestampWrites"] = it }
o["label"] = label
return o
}
Expand Down Expand Up @@ -328,18 +329,39 @@ data class GPURenderPassDepthStencilAttachment(
)

interface GPURenderPassDescriptor

fun GPURenderPassDescriptor(
colorAttachments: Array<GPURenderPassColorAttachment>,
depthStencilAttachment: GPURenderPassDepthStencilAttachment? = null,
timestampWrites: GPURenderPassTimestampWrites? = null,
label: String = ""
): GPURenderPassDescriptor {
val o = js("({})")
o["colorAttachments"] = colorAttachments
depthStencilAttachment?.let { o["depthStencilAttachment"] = it }
timestampWrites?.let { o["timestampWrites"] = it }
o["label"] = label
return o
}

data class GPUComputePassTimestampWrites(
@JsName("querySet")
val querySet: GPUQuerySet,
@JsName("beginningOfPassWriteIndex")
val beginningOfPassWriteIndex: Int,
@JsName("endOfPassWriteIndex")
val endOfPassWriteIndex: Int
)

data class GPURenderPassTimestampWrites(
@JsName("querySet")
val querySet: GPUQuerySet,
@JsName("beginningOfPassWriteIndex")
val beginningOfPassWriteIndex: Int,
@JsName("endOfPassWriteIndex")
val endOfPassWriteIndex: Int
)

interface GPURenderPipelineDescriptor

fun GPURenderPipelineDescriptor(
Expand Down Expand Up @@ -377,6 +399,18 @@ data class GPUDepthStencilState(
val depthBiasClamp: Float = 0f
)

interface GPUDeviceDescriptor

fun GPUDeviceDescriptor(
requiredFeatures: Array<String> = emptyArray(),
label: String = "",
): GPUDeviceDescriptor {
val o = js("({})")
o["requiredFeatures"] = requiredFeatures
o["label"] = label
return o
}

interface GPUProgrammableStage

fun GPUProgrammableStage(
Expand All @@ -390,6 +424,20 @@ fun GPUProgrammableStage(
return o
}

interface GPUQuerySetDescriptor

fun GPUQuerySetDescriptor(
type: GPUQueryType,
count: Int,
label: String = "",
): GPUQuerySetDescriptor {
val o = js("({})")
o["type"] = type
o["count"] = count
o["label"] = label
return o
}

data class GPUShaderModuleDescriptor(
@JsName("code")
val code: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,14 @@ value class GPUPowerPreference private constructor(val enumValue: String) {
}
}

value class GPUQueryType private constructor(val enumValue: String) {
override fun toString() = enumValue
companion object {
val timestamp = GPUQueryType("timestamp")
val occlusion = GPUQueryType("occlusion")
}
}

value class GPUSamplerBindingType private constructor(val enumValue: String) {
override fun toString() = enumValue
companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import de.fabmax.kool.scene.geometry.PrimitiveType
fun GPUCommandEncoder.beginRenderPass(
colorAttachments: Array<GPURenderPassColorAttachment>,
depthStencilAttachment: GPURenderPassDepthStencilAttachment? = null,
timestampWrites: GPURenderPassTimestampWrites? = null,
label: String = ""
) = beginRenderPass(GPURenderPassDescriptor(colorAttachments, depthStencilAttachment, label))
) = beginRenderPass(GPURenderPassDescriptor(colorAttachments, depthStencilAttachment, timestampWrites, label))

fun GPUDevice.createBindGroup(
layout: GPUBindGroupLayout,
Expand Down
Loading

0 comments on commit 1b32d1a

Please sign in to comment.