feat: Compose 改用 Dispatchers.Main 渲染

This commit is contained in:
WCPE
2025-01-14 17:54:27 +08:00
parent 4b515e4903
commit 4a4e2aa296
4 changed files with 108 additions and 96 deletions

View File

@@ -1,7 +1,7 @@
plugins {
kotlin("jvm")
id("maven-publish")
id("fabric-loom") version "1.9-SNAPSHOT"
id("fabric-loom") version "1.9.2"
id("org.jetbrains.compose")
id("org.jetbrains.kotlin.plugin.compose")
@@ -36,6 +36,7 @@ loom {
}
repositories {
mavenLocal()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
google()
@@ -47,9 +48,9 @@ dependencies {
mappings("net.fabricmc:yarn:${project.property("yarn_mappings")}:v2")
modImplementation("net.fabricmc:fabric-loader:${project.property("loader_version")}")
modImplementation("net.fabricmc:fabric-language-kotlin:${project.property("kotlin_loader_version")}")
modImplementation("net.fabricmc.fabric-api:fabric-api:${project.property("fabric_version")}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.9.0")
implementation(compose.desktop.currentOs)
includeInternal(compose.desktop.currentOs) {
exclude(module = "kotlin-stdlib")

View File

@@ -1,5 +1,6 @@
pluginManagement {
repositories {
mavenLocal()
maven("https://maven.fabricmc.net/") {
name = "Fabric"
}

View File

@@ -114,6 +114,9 @@ fun TestUI() {
""".trimIndent()
),
)
for (i in 0..100) {
mails.add(Mail("新手启航大礼包来袭$i", "2025-01-14", """test""".trimIndent()))
}
Column(
modifier = Modifier.size(800.dp, 600.dp).background(Color(0x80808080)),
verticalArrangement = Arrangement.spacedBy(3.dp)

View File

@@ -11,11 +11,10 @@ import androidx.compose.ui.scene.ComposeScene
import androidx.compose.ui.scene.MultiLayerComposeScene
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import com.mojang.blaze3d.platform.GlStateManager
import com.mojang.blaze3d.platform.TextureUtil
import com.mojang.blaze3d.systems.RenderSystem
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Runnable
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.*
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.screen.Screen
@@ -27,17 +26,24 @@ import net.minecraft.text.Text
import org.jetbrains.skia.*
import org.jetbrains.skiko.FrameDispatcher
import org.lwjgl.glfw.GLFW.*
import org.lwjgl.opengl.ARBSync.glClientWaitSync
import org.lwjgl.opengl.ARBSync.glDeleteSync
import org.lwjgl.opengl.ARBSync.glFenceSync
import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL30.*
import org.lwjgl.opengl.GL32.GL_SYNC_FLUSH_COMMANDS_BIT
import org.lwjgl.opengl.GL32C.GL_SYNC_GPU_COMMANDS_COMPLETE
import org.lwjgl.system.MemoryUtil.NULL
import kotlin.coroutines.CoroutineContext
@OptIn(InternalComposeUiApi::class)
class ComposeScreen(
val composeContent: @Composable (ComposeScreen) -> Unit
) : Screen(Text.literal("FrameRenderComposeScreen")) {
companion object {
private val frameBufferID by lazy { GlStateManager.glGenFramebuffers() }
private val textureID by lazy { TextureUtil.generateTextureId() }
private val windowWidth: Int get() = MinecraftClient.getInstance().window.width
private val windowHeight: Int get() = MinecraftClient.getInstance().window.height
private var frameBufferID = -1
private val textureID = TextureUtil.generateTextureId()
private val composeLayer: RenderLayer by lazy {
RenderLayer.of(
"cpu_render_compose",
@@ -54,91 +60,83 @@ class ComposeScreen(
.build(false)
)
}
private val windowID by lazy {
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE)
val oldWindow = glfwGetCurrentContext()
glfwCreateWindow(1, 1, "", NULL, oldWindow)
}
}
private val windowWidth: Int get() = MinecraftClient.getInstance().window.width
private val windowHeight: Int get() = MinecraftClient.getInstance().window.height
private var windowID: Long = -1
private lateinit var directContext: DirectContext
private lateinit var surface: Surface
private lateinit var canvas: Canvas
private lateinit var scene: ComposeScene
private val tasks = mutableListOf<Runnable>()
private val tasksCopy = mutableListOf<Runnable>()
private val mainScope = MainScope()
private val coroutineDispatcher = object : CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
synchronized(tasks) {
tasks.add(block)
private val fenceSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)
private val needRedraw = atomic(true)
private val frameDispatcher = FrameDispatcher(mainScope) {
needRedraw.value = true
}
}
}
private val frameDispatcher = FrameDispatcher(coroutineDispatcher) {
RenderSystem.recordRenderCall {
if (surface.isClosed) {
return@recordRenderCall
}
val oldWindowID = glfwGetCurrentContext()
glfwMakeContextCurrent(windowID)
surface.canvas.clear(Color.TRANSPARENT)
scene.render(canvas, System.nanoTime())
directContext.flush()
glfwMakeContextCurrent(oldWindowID)
private val scene: ComposeScene = MultiLayerComposeScene(
density = Density(MinecraftClient.getInstance().options.guiScale.value.toFloat() / 2),
size = IntSize(windowWidth, windowHeight),
coroutineContext = Dispatchers.Main,
invalidate = { frameDispatcher.scheduleFrame() }
).apply {
setContent {
composeContent(this@ComposeScreen)
}
}
override fun init() {
val oldFrameBuffer = glGetInteger(GL_FRAMEBUFFER_BINDING)
val oldTexture = glGetInteger(GL_TEXTURE_BINDING_2D)
val oldWindow = glfwGetCurrentContext()
glfwMakeContextCurrent(0)
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE)
windowID = glfwCreateWindow(1, 1, "", NULL, oldWindow)
val initTask = mainScope.async {
glfwMakeContextCurrent(windowID)
GL.createCapabilities()
frameBufferID = glGenFramebuffers()
glBindFramebuffer(GL_FRAMEBUFFER, frameBufferID)
glBindTexture(GL_TEXTURE_2D, textureID)
glTexImage2D(
GL_TEXTURE_2D, 0, GL_RGBA, windowWidth, windowHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL
)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, windowWidth, windowHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0)
glBindFramebuffer(GL_FRAMEBUFFER, oldFrameBuffer)
glBindTexture(GL_TEXTURE_2D, oldTexture)
directContext = DirectContext.makeGL()
val renderTarget = BackendRenderTarget.makeGL(
windowWidth, windowHeight, 0, 8, frameBufferID, GL_RGBA8
)
surface = Surface.makeFromBackendRenderTarget(
directContext, renderTarget, SurfaceOrigin.TOP_LEFT, SurfaceColorFormat.RGBA_8888, ColorSpace.sRGB
)!!
) ?: throw IllegalStateException("Surface could not be created")
canvas = surface.canvas.asComposeCanvas()
scene = MultiLayerComposeScene(
density = Density(MinecraftClient.getInstance().options.guiScale.value.toFloat() / 2),
size = IntSize(windowWidth, windowHeight),
coroutineContext = coroutineDispatcher,
invalidate = { frameDispatcher.scheduleFrame() }
)
scene.setContent {
composeContent(this@ComposeScreen)
glfwMakeContextCurrent(0)
}
super.init()
runBlocking {
initTask.await()
}
glfwMakeContextCurrent(oldWindow)
}
override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
synchronized(tasks) {
tasksCopy.clear()
tasksCopy.addAll(tasks)
tasks.clear()
super.render(context, mouseX, mouseY, delta)
if (needRedraw.compareAndSet(true, false)) {
val oldWindow = glfwGetCurrentContext()
glfwMakeContextCurrent(0)
val renderTask = mainScope.async {
if (surface.isClosed) {
return@async
}
for (runnable in tasksCopy) {
runnable.run()
glfwMakeContextCurrent(windowID)
surface.canvas.clear(Color.TRANSPARENT)
scene.render(canvas, System.nanoTime())
directContext.flush()
glfwMakeContextCurrent(0)
}
runBlocking {
renderTask.await()
}
glfwMakeContextCurrent(oldWindow)
glClientWaitSync(fenceSync, GL_SYNC_FLUSH_COMMANDS_BIT, 1000000)
}
context.draw {
val vertexConsumer = it.getBuffer(composeLayer)
@@ -163,7 +161,7 @@ class ComposeScreen(
override fun mouseMoved(mouseX: Double, mouseY: Double) {
val x = MinecraftClient.getInstance().mouse.x.toFloat()
val y = MinecraftClient.getInstance().mouse.y.toFloat()
coroutineDispatcher.dispatch(coroutineDispatcher) {
mainScope.launch {
scene.sendPointerEvent(
PointerEventType.Move,
position = Offset(x, y)
@@ -172,7 +170,7 @@ class ComposeScreen(
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
coroutineDispatcher.dispatch(coroutineDispatcher) {
mainScope.launch {
val x = MinecraftClient.getInstance().mouse.x.toFloat()
val y = MinecraftClient.getInstance().mouse.y.toFloat()
scene.sendPointerEvent(
@@ -185,7 +183,7 @@ class ComposeScreen(
}
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
coroutineDispatcher.dispatch(coroutineDispatcher) {
mainScope.launch {
val x = MinecraftClient.getInstance().mouse.x.toFloat()
val y = MinecraftClient.getInstance().mouse.y.toFloat()
scene.sendPointerEvent(
@@ -203,7 +201,7 @@ class ComposeScreen(
val x = MinecraftClient.getInstance().mouse.x.toFloat()
val y = MinecraftClient.getInstance().mouse.y.toFloat()
println("mouseScrolled: $x $y $horizontalAmount $verticalAmount")
coroutineDispatcher.dispatch(coroutineDispatcher) {
mainScope.launch {
scene.sendPointerEvent(
PointerEventType.Scroll,
button = PointerButton.Tertiary,
@@ -216,8 +214,17 @@ class ComposeScreen(
override fun close() {
MinecraftClient.getInstance().setScreen(null)
if (textureID != 0) {
glDeleteTextures(textureID)
}
if (frameBufferID != -1) {
glDeleteFramebuffers(frameBufferID)
frameBufferID = -1
}
glDeleteSync(fenceSync)
directContext.close()
surface.close()
scene.close()
directContext.close()
mainScope.cancel()
}
}