feat: Compose 改用 Dispatchers.Main 渲染
This commit is contained in:
@@ -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,118 +26,117 @@ 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 composeLayer: RenderLayer by lazy {
|
||||
RenderLayer.of(
|
||||
"cpu_render_compose",
|
||||
VertexFormats.POSITION_TEXTURE,
|
||||
VertexFormat.DrawMode.QUADS,
|
||||
786432,
|
||||
RenderLayer.MultiPhaseParameters.builder()
|
||||
.texture(RenderPhase.TextureBase({
|
||||
RenderSystem.setShaderTexture(0, textureID)
|
||||
}, {}))
|
||||
.program(RenderPhase.POSITION_TEXTURE_PROGRAM)
|
||||
.transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY)
|
||||
.depthTest(RenderPhase.LEQUAL_DEPTH_TEST)
|
||||
.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 frameBufferID = -1
|
||||
private val textureID = TextureUtil.generateTextureId()
|
||||
private val composeLayer: RenderLayer by lazy {
|
||||
RenderLayer.of(
|
||||
"cpu_render_compose",
|
||||
VertexFormats.POSITION_TEXTURE,
|
||||
VertexFormat.DrawMode.QUADS,
|
||||
786432,
|
||||
RenderLayer.MultiPhaseParameters.builder()
|
||||
.texture(RenderPhase.TextureBase({
|
||||
RenderSystem.setShaderTexture(0, textureID)
|
||||
}, {}))
|
||||
.program(RenderPhase.POSITION_TEXTURE_PROGRAM)
|
||||
.transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY)
|
||||
.depthTest(RenderPhase.LEQUAL_DEPTH_TEST)
|
||||
.build(false)
|
||||
)
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
)
|
||||
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)
|
||||
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)
|
||||
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
|
||||
)!!
|
||||
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)
|
||||
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()
|
||||
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()
|
||||
}
|
||||
for (runnable in tasksCopy) {
|
||||
runnable.run()
|
||||
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
|
||||
}
|
||||
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