feat: Compose 改用 Dispatchers.Main 渲染
This commit is contained in:
+3
-2
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
id("maven-publish")
|
id("maven-publish")
|
||||||
id("fabric-loom") version "1.9-SNAPSHOT"
|
id("fabric-loom") version "1.9.2"
|
||||||
|
|
||||||
id("org.jetbrains.compose")
|
id("org.jetbrains.compose")
|
||||||
id("org.jetbrains.kotlin.plugin.compose")
|
id("org.jetbrains.kotlin.plugin.compose")
|
||||||
@@ -36,6 +36,7 @@ loom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
|
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
|
||||||
google()
|
google()
|
||||||
@@ -47,9 +48,9 @@ dependencies {
|
|||||||
mappings("net.fabricmc:yarn:${project.property("yarn_mappings")}:v2")
|
mappings("net.fabricmc:yarn:${project.property("yarn_mappings")}:v2")
|
||||||
modImplementation("net.fabricmc:fabric-loader:${project.property("loader_version")}")
|
modImplementation("net.fabricmc:fabric-loader:${project.property("loader_version")}")
|
||||||
modImplementation("net.fabricmc:fabric-language-kotlin:${project.property("kotlin_loader_version")}")
|
modImplementation("net.fabricmc:fabric-language-kotlin:${project.property("kotlin_loader_version")}")
|
||||||
|
|
||||||
modImplementation("net.fabricmc.fabric-api:fabric-api:${project.property("fabric_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)
|
implementation(compose.desktop.currentOs)
|
||||||
includeInternal(compose.desktop.currentOs) {
|
includeInternal(compose.desktop.currentOs) {
|
||||||
exclude(module = "kotlin-stdlib")
|
exclude(module = "kotlin-stdlib")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
repositories {
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
maven("https://maven.fabricmc.net/") {
|
maven("https://maven.fabricmc.net/") {
|
||||||
name = "Fabric"
|
name = "Fabric"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,9 @@ fun TestUI() {
|
|||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
for (i in 0..100) {
|
||||||
|
mails.add(Mail("新手启航大礼包来袭$i", "2025-01-14", """test""".trimIndent()))
|
||||||
|
}
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.size(800.dp, 600.dp).background(Color(0x80808080)),
|
modifier = Modifier.size(800.dp, 600.dp).background(Color(0x80808080)),
|
||||||
verticalArrangement = Arrangement.spacedBy(3.dp)
|
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.scene.MultiLayerComposeScene
|
||||||
import androidx.compose.ui.unit.Density
|
import androidx.compose.ui.unit.Density
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import com.mojang.blaze3d.platform.GlStateManager
|
|
||||||
import com.mojang.blaze3d.platform.TextureUtil
|
import com.mojang.blaze3d.platform.TextureUtil
|
||||||
import com.mojang.blaze3d.systems.RenderSystem
|
import com.mojang.blaze3d.systems.RenderSystem
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.atomicfu.atomic
|
||||||
import kotlinx.coroutines.Runnable
|
import kotlinx.coroutines.*
|
||||||
import net.minecraft.client.MinecraftClient
|
import net.minecraft.client.MinecraftClient
|
||||||
import net.minecraft.client.gui.DrawContext
|
import net.minecraft.client.gui.DrawContext
|
||||||
import net.minecraft.client.gui.screen.Screen
|
import net.minecraft.client.gui.screen.Screen
|
||||||
@@ -27,17 +26,24 @@ import net.minecraft.text.Text
|
|||||||
import org.jetbrains.skia.*
|
import org.jetbrains.skia.*
|
||||||
import org.jetbrains.skiko.FrameDispatcher
|
import org.jetbrains.skiko.FrameDispatcher
|
||||||
import org.lwjgl.glfw.GLFW.*
|
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.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 org.lwjgl.system.MemoryUtil.NULL
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
@OptIn(InternalComposeUiApi::class)
|
@OptIn(InternalComposeUiApi::class)
|
||||||
class ComposeScreen(
|
class ComposeScreen(
|
||||||
val composeContent: @Composable (ComposeScreen) -> Unit
|
val composeContent: @Composable (ComposeScreen) -> Unit
|
||||||
) : Screen(Text.literal("FrameRenderComposeScreen")) {
|
) : Screen(Text.literal("FrameRenderComposeScreen")) {
|
||||||
companion object {
|
private val windowWidth: Int get() = MinecraftClient.getInstance().window.width
|
||||||
private val frameBufferID by lazy { GlStateManager.glGenFramebuffers() }
|
private val windowHeight: Int get() = MinecraftClient.getInstance().window.height
|
||||||
private val textureID by lazy { TextureUtil.generateTextureId() }
|
|
||||||
|
private var frameBufferID = -1
|
||||||
|
private val textureID = TextureUtil.generateTextureId()
|
||||||
private val composeLayer: RenderLayer by lazy {
|
private val composeLayer: RenderLayer by lazy {
|
||||||
RenderLayer.of(
|
RenderLayer.of(
|
||||||
"cpu_render_compose",
|
"cpu_render_compose",
|
||||||
@@ -54,91 +60,83 @@ class ComposeScreen(
|
|||||||
.build(false)
|
.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 directContext: DirectContext
|
||||||
private lateinit var surface: Surface
|
private lateinit var surface: Surface
|
||||||
private lateinit var canvas: Canvas
|
private lateinit var canvas: Canvas
|
||||||
private lateinit var scene: ComposeScene
|
|
||||||
|
|
||||||
private val tasks = mutableListOf<Runnable>()
|
private val mainScope = MainScope()
|
||||||
private val tasksCopy = mutableListOf<Runnable>()
|
|
||||||
|
|
||||||
private val coroutineDispatcher = object : CoroutineDispatcher() {
|
private val fenceSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)
|
||||||
override fun dispatch(context: CoroutineContext, block: Runnable) {
|
private val needRedraw = atomic(true)
|
||||||
synchronized(tasks) {
|
private val frameDispatcher = FrameDispatcher(mainScope) {
|
||||||
tasks.add(block)
|
needRedraw.value = true
|
||||||
}
|
}
|
||||||
}
|
private val scene: ComposeScene = MultiLayerComposeScene(
|
||||||
}
|
density = Density(MinecraftClient.getInstance().options.guiScale.value.toFloat() / 2),
|
||||||
private val frameDispatcher = FrameDispatcher(coroutineDispatcher) {
|
size = IntSize(windowWidth, windowHeight),
|
||||||
RenderSystem.recordRenderCall {
|
coroutineContext = Dispatchers.Main,
|
||||||
if (surface.isClosed) {
|
invalidate = { frameDispatcher.scheduleFrame() }
|
||||||
return@recordRenderCall
|
).apply {
|
||||||
}
|
setContent {
|
||||||
val oldWindowID = glfwGetCurrentContext()
|
composeContent(this@ComposeScreen)
|
||||||
glfwMakeContextCurrent(windowID)
|
|
||||||
|
|
||||||
surface.canvas.clear(Color.TRANSPARENT)
|
|
||||||
scene.render(canvas, System.nanoTime())
|
|
||||||
directContext.flush()
|
|
||||||
|
|
||||||
glfwMakeContextCurrent(oldWindowID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
val oldFrameBuffer = glGetInteger(GL_FRAMEBUFFER_BINDING)
|
val oldWindow = glfwGetCurrentContext()
|
||||||
val oldTexture = glGetInteger(GL_TEXTURE_BINDING_2D)
|
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)
|
glBindFramebuffer(GL_FRAMEBUFFER, frameBufferID)
|
||||||
glBindTexture(GL_TEXTURE_2D, textureID)
|
glBindTexture(GL_TEXTURE_2D, textureID)
|
||||||
glTexImage2D(
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, windowWidth, windowHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL)
|
||||||
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_MIN_FILTER, GL_LINEAR)
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0)
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0)
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, oldFrameBuffer)
|
|
||||||
glBindTexture(GL_TEXTURE_2D, oldTexture)
|
|
||||||
|
|
||||||
directContext = DirectContext.makeGL()
|
directContext = DirectContext.makeGL()
|
||||||
val renderTarget = BackendRenderTarget.makeGL(
|
val renderTarget = BackendRenderTarget.makeGL(
|
||||||
windowWidth, windowHeight, 0, 8, frameBufferID, GL_RGBA8
|
windowWidth, windowHeight, 0, 8, frameBufferID, GL_RGBA8
|
||||||
)
|
)
|
||||||
surface = Surface.makeFromBackendRenderTarget(
|
surface = Surface.makeFromBackendRenderTarget(
|
||||||
directContext, renderTarget, SurfaceOrigin.TOP_LEFT, SurfaceColorFormat.RGBA_8888, ColorSpace.sRGB
|
directContext, renderTarget, SurfaceOrigin.TOP_LEFT, SurfaceColorFormat.RGBA_8888, ColorSpace.sRGB
|
||||||
)!!
|
) ?: throw IllegalStateException("Surface could not be created")
|
||||||
canvas = surface.canvas.asComposeCanvas()
|
canvas = surface.canvas.asComposeCanvas()
|
||||||
scene = MultiLayerComposeScene(
|
glfwMakeContextCurrent(0)
|
||||||
density = Density(MinecraftClient.getInstance().options.guiScale.value.toFloat() / 2),
|
|
||||||
size = IntSize(windowWidth, windowHeight),
|
|
||||||
coroutineContext = coroutineDispatcher,
|
|
||||||
invalidate = { frameDispatcher.scheduleFrame() }
|
|
||||||
)
|
|
||||||
scene.setContent {
|
|
||||||
composeContent(this@ComposeScreen)
|
|
||||||
}
|
}
|
||||||
super.init()
|
runBlocking {
|
||||||
|
initTask.await()
|
||||||
|
}
|
||||||
|
glfwMakeContextCurrent(oldWindow)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
|
override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
|
||||||
synchronized(tasks) {
|
super.render(context, mouseX, mouseY, delta)
|
||||||
tasksCopy.clear()
|
if (needRedraw.compareAndSet(true, false)) {
|
||||||
tasksCopy.addAll(tasks)
|
val oldWindow = glfwGetCurrentContext()
|
||||||
tasks.clear()
|
glfwMakeContextCurrent(0)
|
||||||
|
val renderTask = mainScope.async {
|
||||||
|
if (surface.isClosed) {
|
||||||
|
return@async
|
||||||
}
|
}
|
||||||
for (runnable in tasksCopy) {
|
glfwMakeContextCurrent(windowID)
|
||||||
runnable.run()
|
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 {
|
context.draw {
|
||||||
val vertexConsumer = it.getBuffer(composeLayer)
|
val vertexConsumer = it.getBuffer(composeLayer)
|
||||||
@@ -163,7 +161,7 @@ class ComposeScreen(
|
|||||||
override fun mouseMoved(mouseX: Double, mouseY: Double) {
|
override fun mouseMoved(mouseX: Double, mouseY: Double) {
|
||||||
val x = MinecraftClient.getInstance().mouse.x.toFloat()
|
val x = MinecraftClient.getInstance().mouse.x.toFloat()
|
||||||
val y = MinecraftClient.getInstance().mouse.y.toFloat()
|
val y = MinecraftClient.getInstance().mouse.y.toFloat()
|
||||||
coroutineDispatcher.dispatch(coroutineDispatcher) {
|
mainScope.launch {
|
||||||
scene.sendPointerEvent(
|
scene.sendPointerEvent(
|
||||||
PointerEventType.Move,
|
PointerEventType.Move,
|
||||||
position = Offset(x, y)
|
position = Offset(x, y)
|
||||||
@@ -172,7 +170,7 @@ class ComposeScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||||
coroutineDispatcher.dispatch(coroutineDispatcher) {
|
mainScope.launch {
|
||||||
val x = MinecraftClient.getInstance().mouse.x.toFloat()
|
val x = MinecraftClient.getInstance().mouse.x.toFloat()
|
||||||
val y = MinecraftClient.getInstance().mouse.y.toFloat()
|
val y = MinecraftClient.getInstance().mouse.y.toFloat()
|
||||||
scene.sendPointerEvent(
|
scene.sendPointerEvent(
|
||||||
@@ -185,7 +183,7 @@ class ComposeScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||||
coroutineDispatcher.dispatch(coroutineDispatcher) {
|
mainScope.launch {
|
||||||
val x = MinecraftClient.getInstance().mouse.x.toFloat()
|
val x = MinecraftClient.getInstance().mouse.x.toFloat()
|
||||||
val y = MinecraftClient.getInstance().mouse.y.toFloat()
|
val y = MinecraftClient.getInstance().mouse.y.toFloat()
|
||||||
scene.sendPointerEvent(
|
scene.sendPointerEvent(
|
||||||
@@ -203,7 +201,7 @@ class ComposeScreen(
|
|||||||
val x = MinecraftClient.getInstance().mouse.x.toFloat()
|
val x = MinecraftClient.getInstance().mouse.x.toFloat()
|
||||||
val y = MinecraftClient.getInstance().mouse.y.toFloat()
|
val y = MinecraftClient.getInstance().mouse.y.toFloat()
|
||||||
println("mouseScrolled: $x $y $horizontalAmount $verticalAmount")
|
println("mouseScrolled: $x $y $horizontalAmount $verticalAmount")
|
||||||
coroutineDispatcher.dispatch(coroutineDispatcher) {
|
mainScope.launch {
|
||||||
scene.sendPointerEvent(
|
scene.sendPointerEvent(
|
||||||
PointerEventType.Scroll,
|
PointerEventType.Scroll,
|
||||||
button = PointerButton.Tertiary,
|
button = PointerButton.Tertiary,
|
||||||
@@ -216,8 +214,17 @@ class ComposeScreen(
|
|||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
MinecraftClient.getInstance().setScreen(null)
|
MinecraftClient.getInstance().setScreen(null)
|
||||||
|
if (textureID != 0) {
|
||||||
|
glDeleteTextures(textureID)
|
||||||
|
}
|
||||||
|
if (frameBufferID != -1) {
|
||||||
|
glDeleteFramebuffers(frameBufferID)
|
||||||
|
frameBufferID = -1
|
||||||
|
}
|
||||||
|
glDeleteSync(fenceSync)
|
||||||
|
directContext.close()
|
||||||
surface.close()
|
surface.close()
|
||||||
scene.close()
|
scene.close()
|
||||||
directContext.close()
|
mainScope.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user