feat: Compose 改用 Dispatchers.Main 渲染
This commit is contained in:
+3
-2
@@ -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")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven("https://maven.fabricmc.net/") {
|
||||
name = "Fabric"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user