Skip to content

Commit

Permalink
start working on js WebGPU backend
Browse files Browse the repository at this point in the history
  • Loading branch information
fabmax committed Dec 27, 2023
1 parent 2cc8b86 commit 105e7aa
Show file tree
Hide file tree
Showing 26 changed files with 238 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ actual fun defaultKoolConfig(): KoolConfig {
TODO()
}

actual fun createContext(): KoolContext {
TODO()
}

actual fun KoolApplication(config: KoolConfig, appBlock: (KoolContext) -> Unit) {
actual fun createContext(config: KoolConfig): KoolContext {
TODO()
}
4 changes: 0 additions & 4 deletions kool-core/src/commonMain/kotlin/de/fabmax/kool/KoolContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,6 @@ abstract class KoolContext {

abstract fun run()

open fun close() {
backend.close(this)
}

abstract fun getSysInfos(): List<String>

fun getWindowViewport(result: Viewport) {
Expand Down
8 changes: 6 additions & 2 deletions kool-core/src/commonMain/kotlin/de/fabmax/kool/Platform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ expect fun defaultKoolConfig(): KoolConfig
* Creates a new [KoolContext] based on the [KoolConfig] provided by [KoolSystem]. [KoolSystem.initialize] has to be
* called before invoking this function.
*/
expect fun createContext(): KoolContext
expect fun createContext(config: KoolConfig = defaultKoolConfig()): KoolContext

expect fun KoolApplication(config: KoolConfig = defaultKoolConfig(), appBlock: (KoolContext) -> Unit)
fun KoolApplication(config: KoolConfig = defaultKoolConfig(), appBlock: (KoolContext) -> Unit) {
val ctx = createContext(config)
appBlock(ctx)
ctx.run()
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.fabmax.kool.pipeline.backend

import de.fabmax.kool.KoolContext
import de.fabmax.kool.KoolSystem
import de.fabmax.kool.modules.ksl.KslComputeShader
import de.fabmax.kool.modules.ksl.KslShader
import de.fabmax.kool.pipeline.*
Expand All @@ -17,10 +18,12 @@ interface RenderBackend {
val isOnscreenInfiniteDepthCapable: Boolean

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

fun getWindowViewport(result: Viewport)
fun getWindowViewport(result: Viewport) {
val ctx = KoolSystem.requireContext()
result.set(0, 0, ctx.windowWidth, ctx.windowHeight)
}

fun generateKslShader(shader: KslShader, pipeline: Pipeline): ShaderCode
fun generateKslComputeShader(shader: KslComputeShader, pipeline: ComputePipeline): ComputeShaderCode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,9 @@ val KoolSystem.configJvm: KoolConfigJvm get() = config as KoolConfigJvm
* Creates a new [KoolContext] based on the [KoolConfig] provided by [KoolSystem]. [KoolSystem.initialize] has to be
* called before invoking this function.
*/
actual fun createContext() = DesktopImpl.createContext()

actual fun KoolApplication(config: KoolConfig, appBlock: (KoolContext) -> Unit) {
actual fun createContext(config: KoolConfig): KoolContext {
KoolSystem.initialize(config)
val ctx = createContext()
appBlock(ctx)
ctx.run()
return DesktopImpl.createContext()
}

actual fun Double.toString(precision: Int): String = "%.${precision.clamp(0, 12)}f".format(Locale.ENGLISH, this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import de.fabmax.kool.configJvm
import de.fabmax.kool.platform.GlfwWindow
import de.fabmax.kool.platform.Lwjgl3Context
import de.fabmax.kool.platform.RenderBackendJvm
import de.fabmax.kool.util.Viewport
import org.lwjgl.glfw.GLFW
import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose
import org.lwjgl.glfw.GLFW.glfwSwapBuffers
import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL11.glEnable
Expand Down Expand Up @@ -37,10 +35,6 @@ class RenderBackendGlImpl(ctx: KoolContext) : RenderBackendGl(GlImpl, ctx), Rend
setupGl()
}

override fun getWindowViewport(result: Viewport) {
result.set(0, 0, glfwWindow.framebufferWidth, glfwWindow.framebufferHeight)
}

override fun renderFrame(ctx: KoolContext) {
super.renderFrame(ctx)
glfwSwapBuffers(glfwWindow.windowPtr)
Expand Down Expand Up @@ -72,10 +66,6 @@ class RenderBackendGlImpl(ctx: KoolContext) : RenderBackendGl(GlImpl, ctx), Rend
return glfwWindow
}

override fun close(ctx: KoolContext) {
glfwSetWindowShouldClose(glfwWindow.windowPtr, true)
}

override fun cleanup(ctx: KoolContext) {
// for now, we leave the cleanup to the system...
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import de.fabmax.kool.util.Color
import de.fabmax.kool.util.Viewport
import de.fabmax.kool.util.memStack
import org.lwjgl.PointerBuffer
import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose
import org.lwjgl.system.MemoryStack
import org.lwjgl.vulkan.VK10.*
import org.lwjgl.vulkan.VkClearValue
Expand Down Expand Up @@ -55,10 +54,6 @@ class VkRenderBackend(val ctx: Lwjgl3Context) : RenderBackendJvm {
deviceName = vkSystem.physicalDevice.deviceName
}

override fun getWindowViewport(result: Viewport) {
result.set(0, 0, glfwWindow.framebufferWidth, glfwWindow.framebufferHeight)
}

override fun uploadTextureToGpu(tex: Texture, data: TextureData) {
tex.loadedTexture = when (tex) {
is Texture1d -> TextureLoader.loadTexture1d(vkSystem, tex.props, data)
Expand Down Expand Up @@ -105,10 +100,6 @@ class VkRenderBackend(val ctx: Lwjgl3Context) : RenderBackendJvm {
vkSystem.renderLoop.drawFrame()
}

override fun close(ctx: KoolContext) {
glfwSetWindowShouldClose(vkSystem.window.windowPtr, true)
}

override fun cleanup(ctx: KoolContext) {
vkDeviceWaitIdle(vkSystem.device.vkDevice)
vkSystem.destroy()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import de.fabmax.kool.math.clamp
import de.fabmax.kool.pipeline.backend.gl.RenderBackendGlImpl
import de.fabmax.kool.pipeline.backend.vk.VkRenderBackend
import de.fabmax.kool.util.RenderLoopCoroutineDispatcher
import org.lwjgl.glfw.GLFW.glfwPollEvents
import org.lwjgl.glfw.GLFW.glfwWindowShouldClose
import org.lwjgl.glfw.GLFW.*
import java.awt.Desktop
import java.awt.image.BufferedImage
import java.net.URI
Expand Down Expand Up @@ -93,6 +92,10 @@ class Lwjgl3Context : KoolContext() {

override fun openUrl(url: String, sameWindow: Boolean) = Desktop.getDesktop().browse(URI(url))

fun close() {
glfwSetWindowShouldClose(backend.glfwWindow.windowPtr, true)
}

override fun run() {
while (!glfwWindowShouldClose(backend.glfwWindow.windowPtr)) {
SysInfo.update()
Expand Down
2 changes: 1 addition & 1 deletion kool-core/src/jsMain/kotlin/de/fabmax/kool/KoolConfigJs.kt

Large diffs are not rendered by default.

8 changes: 2 additions & 6 deletions kool-core/src/jsMain/kotlin/de/fabmax/kool/Platform.js.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,9 @@ val KoolSystem.configJs: KoolConfigJs get() = config as KoolConfigJs
* Creates a new [KoolContext] based on the [KoolConfig] provided by [KoolSystem]. [KoolSystem.initialize] has to be
* called before invoking this function.
*/
actual fun createContext() = JsImpl.createContext()

actual fun KoolApplication(config: KoolConfig, appBlock: (KoolContext) -> Unit) {
actual fun createContext(config: KoolConfig): KoolContext {
KoolSystem.initialize(config)
val ctx = createContext()
appBlock(ctx)
ctx.run()
return JsImpl.createContext()
}

actual fun Double.toString(precision: Int): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package de.fabmax.kool.pipeline.backend

interface JsRenderBackend {
suspend fun startRenderLoop()
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import de.fabmax.kool.KoolSystem
import de.fabmax.kool.modules.ksl.KslUnlitShader
import de.fabmax.kool.modules.ksl.lang.xy
import de.fabmax.kool.pipeline.*
import de.fabmax.kool.pipeline.backend.JsRenderBackend
import de.fabmax.kool.platform.JsContext
import de.fabmax.kool.scene.Node
import de.fabmax.kool.scene.Scene
import de.fabmax.kool.scene.addTextureMesh
import de.fabmax.kool.util.Viewport
import kotlinx.browser.window
import org.w3c.dom.HTMLCanvasElement

class RenderBackendGlImpl(ctx: KoolContext, canvas: HTMLCanvasElement) : RenderBackendGl(GlImpl, ctx) {
class RenderBackendGlImpl(ctx: KoolContext, canvas: HTMLCanvasElement) : RenderBackendGl(GlImpl, ctx), JsRenderBackend {
override val deviceName = "WebGL"

override val glslGeneratorHints: GlslGenerator.Hints = GlslGenerator.Hints(
Expand Down Expand Up @@ -39,18 +42,19 @@ class RenderBackendGlImpl(ctx: KoolContext, canvas: HTMLCanvasElement) : RenderB
setupGl()
}

override fun getWindowViewport(result: Viewport) {
result.set(0, 0, ctx.windowWidth, ctx.windowHeight)
}

override fun close(ctx: KoolContext) {
// nothing to do here
override suspend fun startRenderLoop() {
window.requestAnimationFrame { t -> (ctx as JsContext).renderFrame(t) }
}

override fun cleanup(ctx: KoolContext) {
// for now, we leave the cleanup to the system...
}

override fun renderFrame(ctx: KoolContext) {
super.renderFrame(ctx)
GlImpl.gl.finish()
}

override fun blitFrameBuffers(
src: OffscreenRenderPass2d,
dst: OffscreenRenderPass2dGl?,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package de.fabmax.kool.pipeline.backend.webgpu

import de.fabmax.kool.KoolContext
import de.fabmax.kool.modules.ksl.KslComputeShader
import de.fabmax.kool.modules.ksl.KslShader
import de.fabmax.kool.pipeline.*
import de.fabmax.kool.pipeline.backend.DepthRange
import de.fabmax.kool.pipeline.backend.JsRenderBackend
import de.fabmax.kool.pipeline.backend.RenderBackend
import de.fabmax.kool.pipeline.backend.stats.BackendStats
import de.fabmax.kool.platform.JsContext
import de.fabmax.kool.scene.Scene
import de.fabmax.kool.util.LongHash
import de.fabmax.kool.util.logE
import de.fabmax.kool.util.logI
import de.fabmax.kool.util.logT
import kotlinx.browser.window
import kotlinx.coroutines.await
import org.w3c.dom.HTMLCanvasElement

class RenderBackendWebGpu(val ctx: KoolContext, val canvas: HTMLCanvasElement) : RenderBackend, JsRenderBackend {
override val name: String = "WebGPU Backend"
override val apiName: String = "WebGPU"
override val deviceName: String = "WebGPU"
override val depthRange: DepthRange = DepthRange.ZERO_TO_ONE
override val canBlitRenderPasses: Boolean = false
override val isOnscreenInfiniteDepthCapable: Boolean = false // actually it can...

private lateinit var adapter: GPUAdapter
private lateinit var device: GPUDevice
private lateinit var gpuContext: GPUCanvasContext

init {
check(!js("!navigator.gpu") as Boolean) {
val txt = "WebGPU not supported on this browser."
js("alert(txt)")
txt
}
}

override suspend fun startRenderLoop() {
adapter = checkNotNull(navigator.gpu.requestAdapter().await()) {
val txt = "No appropriate GPUAdapter found."
js("alert(txt)")
txt
}

device = adapter.requestDevice().await()

gpuContext = canvas.getContext("webgpu") as GPUCanvasContext
val canvasFormat = navigator.gpu.getPreferredCanvasFormat()
gpuContext.configure(GPUCanvasConfiguration(device, canvasFormat))
logI { "WebGPU context created" }

window.requestAnimationFrame { t -> (ctx as JsContext).renderFrame(t) }
}

override fun renderFrame(ctx: KoolContext) {
BackendStats.resetPerFrameCounts()

// if (ctx.disposablePipelines.isNotEmpty()) {
// ctx.disposablePipelines.forEach {
// shaderMgr.deleteShader(it)
// }
// ctx.disposablePipelines.clear()
// }

// doOffscreenPasses(ctx.backgroundScene, ctx)

for (i in ctx.scenes.indices) {
val scene = ctx.scenes[i]
if (scene.isVisible) {
// if (scene.framebufferCaptureMode == Scene.FramebufferCaptureMode.BeforeRender) {
// captureFramebuffer(scene)
// }
// doOffscreenPasses(scene, ctx)
doForegroundPass(scene)
}
}
}

fun doForegroundPass(scene: Scene) {
val clearColor = scene.mainRenderPass.clearColor ?: return

val encoder = device.createCommandEncoder()
val pass = encoder.beginRenderPass(GPURenderPassDescriptor(arrayOf(
GPURenderPassColorAttachment(
view = gpuContext.getCurrentTexture().createView(),
clearValue = GPUColorDict(clearColor.r.toDouble(), clearColor.g.toDouble(), clearColor.b.toDouble(), clearColor.a.toDouble()),
loadOp = GPULoadOp.clear,
storeOp = GPUStoreOp.store
)
)))

pass.end()
device.queue.submit(arrayOf(encoder.finish()))
}

override fun cleanup(ctx: KoolContext) {
// do nothing for now
}

override fun generateKslShader(shader: KslShader, pipeline: Pipeline): ShaderCode {
logE { "Not yet implemented: WebGpuShaderCode()" }
return WebGpuShaderCode()
}

override fun generateKslComputeShader(shader: KslComputeShader, pipeline: ComputePipeline): ComputeShaderCode {
logE { "Not yet implemented: WebGpuComputeShaderCode()" }
return WebGpuComputeShaderCode()
}

override fun createOffscreenPass2d(parentPass: OffscreenRenderPass2d): OffscreenPass2dImpl {
return WebGpuOffscreenPass2d(parentPass)
}

override fun createOffscreenPassCube(parentPass: OffscreenRenderPassCube): OffscreenPassCubeImpl {
return WebGpuOffscreenPassCube(parentPass)
}

override fun uploadTextureToGpu(tex: Texture, data: TextureData) {
logE { "Not yet implemented: uploadTextureToGpu()" }
}

class WebGpuOffscreenPass2d(val parentPass: OffscreenRenderPass2d) : OffscreenPass2dImpl {
override val isReverseDepth: Boolean = false

override fun applySize(width: Int, height: Int) { }

override fun release() { }

override fun draw(ctx: KoolContext) {
logT { "Draw 2d: ${parentPass.name}" }
}
}

class WebGpuOffscreenPassCube(val parentPass: OffscreenRenderPassCube) : OffscreenPassCubeImpl {
override val isReverseDepth: Boolean = false

override fun applySize(width: Int, height: Int) { }

override fun release() { }

override fun draw(ctx: KoolContext) {
logT { "Draw cube: ${parentPass.name}" }
}
}

class WebGpuShaderCode: ShaderCode {
override val hash: LongHash = LongHash()
}

class WebGpuComputeShaderCode: ComputeShaderCode {
override val hash: LongHash = LongHash()
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.fabmax.kool.platform.webgpu
package de.fabmax.kool.pipeline.backend.webgpu

class GPUBindGroupLayoutDescriptor(
@JsName("entries")
Expand Down Expand Up @@ -127,8 +127,8 @@ class GPUPrimitiveState(topology: GPUPrimitiveTopology) {
class GPURenderPassColorAttachment(
@JsName("view")
val view: GPUTextureView,
@JsName("resolveTarget")
val resolveTarget: GPUTextureView,
// @JsName("resolveTarget")
// val resolveTarget: GPUTextureView,
@JsName("clearValue")
val clearValue: GPUColorDict,
@JsName("loadOp")
Expand Down
Loading

0 comments on commit 105e7aa

Please sign in to comment.