-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
unstable: Import AlphaEffect, GradientEffect, and related from Essential
Source-Commit: 1ecfce45f56459000f495b98bd4a3308be228e06 Co-authored-by: Jonas Herzig <[email protected]> Co-authored-by: DJtheRedstoner <[email protected]>
- Loading branch information
1 parent
f66a554
commit ddd0c43
Showing
6 changed files
with
414 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
187 changes: 187 additions & 0 deletions
187
unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/effects/AlphaEffect.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
package gg.essential.elementa.effects | ||
|
||
import gg.essential.elementa.effects.Effect | ||
import gg.essential.elementa.state.State | ||
import gg.essential.universal.UGraphics | ||
import gg.essential.universal.UMatrixStack | ||
import gg.essential.universal.UResolution | ||
import gg.essential.universal.shader.BlendState | ||
import gg.essential.universal.shader.SamplerUniform | ||
import gg.essential.universal.shader.UShader | ||
import org.lwjgl.opengl.GL11 | ||
import java.io.Closeable | ||
import java.lang.ref.PhantomReference | ||
import java.lang.ref.ReferenceQueue | ||
import java.nio.ByteBuffer | ||
import java.util.* | ||
import java.util.concurrent.ConcurrentHashMap | ||
|
||
/** | ||
* Applies an alpha value to a component. This is done by snapshotting the framebuffer behind the component, | ||
* rendering the component, then rendering the snapshot with the inverse of the desired alpha. | ||
*/ | ||
class AlphaEffect(private val alphaState: State<Float>) : Effect() { | ||
private val resources = Resources(this) | ||
private var textureWidth = -1 | ||
private var textureHeight = -1 | ||
|
||
override fun setup() { | ||
initShader() | ||
Resources.drainCleanupQueue() | ||
resources.textureId = GL11.glGenTextures() | ||
} | ||
|
||
override fun beforeDraw(matrixStack: UMatrixStack) { | ||
if (resources.textureId == -1) error("AlphaEffect has not yet been setup or has already been cleaned up! ElementaVersion.V4 or newer is required for proper operation!") | ||
|
||
val scale = UResolution.scaleFactor | ||
|
||
// Get the coordinates of the component within the bounds of the screen in real pixels | ||
val left = (boundComponent.getLeft() * scale).toInt().coerceIn(0..UResolution.viewportWidth) | ||
val right = (boundComponent.getRight() * scale).toInt().coerceIn(0..UResolution.viewportWidth) | ||
val top = (boundComponent.getTop() * scale).toInt().coerceIn(0..UResolution.viewportHeight) | ||
val bottom = (boundComponent.getBottom() * scale).toInt().coerceIn(0..UResolution.viewportHeight) | ||
|
||
val x = left | ||
val y = UResolution.viewportHeight - bottom // OpenGL screen coordinates start in the bottom left | ||
val width = right - left | ||
val height = bottom - top | ||
|
||
if (width == 0 || height == 0 || !shader.usable) { | ||
return | ||
} | ||
|
||
UGraphics.configureTexture(resources.textureId) { | ||
if (width != textureWidth || height != textureHeight) { | ||
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, null as ByteBuffer?) | ||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST) | ||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST) | ||
textureWidth = width | ||
textureHeight = height | ||
} | ||
|
||
GL11.glCopyTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, x, y, width, height) | ||
} | ||
} | ||
|
||
override fun afterDraw(matrixStack: UMatrixStack) { | ||
// Get the coordinates of the component within the bounds of the screen in fractional MC pixels | ||
val left = boundComponent.getLeft().toDouble().coerceIn(0.0..UResolution.viewportWidth / UResolution.scaleFactor) | ||
val right = boundComponent.getRight().toDouble().coerceIn(0.0..UResolution.viewportWidth / UResolution.scaleFactor) | ||
val top = boundComponent.getTop().toDouble().coerceIn(0.0..UResolution.viewportHeight / UResolution.scaleFactor) | ||
val bottom = boundComponent.getBottom().toDouble().coerceIn(0.0..UResolution.viewportHeight / UResolution.scaleFactor) | ||
|
||
val x = left | ||
val y = top | ||
val width = right - left | ||
val height = bottom - top | ||
|
||
if (width == 0.0 || height == 0.0 || !shader.usable) { | ||
return | ||
} | ||
|
||
val red = 1f | ||
val green = 1f | ||
val blue = 1f | ||
val alpha = 1f - alphaState.get() | ||
|
||
var prevAlphaTestFunc = 0 | ||
var prevAlphaTestRef = 0f | ||
if (!UGraphics.isCoreProfile()) { | ||
prevAlphaTestFunc = GL11.glGetInteger(GL11.GL_ALPHA_TEST_FUNC) | ||
prevAlphaTestRef = GL11.glGetFloat(GL11.GL_ALPHA_TEST_REF) | ||
UGraphics.alphaFunc(GL11.GL_ALWAYS, 0f) | ||
} | ||
|
||
shader.bind() | ||
textureUniform.setValue(resources.textureId) | ||
|
||
val worldRenderer = UGraphics.getFromTessellator() | ||
worldRenderer.beginWithActiveShader(UGraphics.DrawMode.QUADS, UGraphics.CommonVertexFormats.POSITION_TEXTURE_COLOR) | ||
worldRenderer.pos(matrixStack, x, y + height, 0.0).tex(0.0, 0.0).color(red, green, blue, alpha).endVertex() | ||
worldRenderer.pos(matrixStack, x + width, y + height, 0.0).tex(1.0, 0.0).color(red, green, blue, alpha).endVertex() | ||
worldRenderer.pos(matrixStack, x + width, y, 0.0).tex(1.0, 1.0).color(red, green, blue, alpha).endVertex() | ||
worldRenderer.pos(matrixStack, x, y, 0.0).tex(0.0, 1.0).color(red, green, blue, alpha).endVertex() | ||
worldRenderer.drawDirect() | ||
|
||
shader.unbind() | ||
|
||
if (!UGraphics.isCoreProfile()) { | ||
UGraphics.alphaFunc(prevAlphaTestFunc, prevAlphaTestRef) | ||
} | ||
} | ||
|
||
fun cleanup() { | ||
resources.close() | ||
} | ||
|
||
private class Resources(effect: AlphaEffect) : PhantomReference<AlphaEffect>(effect, referenceQueue), Closeable { | ||
var textureId = -1 | ||
|
||
init { | ||
toBeCleanedUp.add(this) | ||
} | ||
|
||
override fun close() { | ||
toBeCleanedUp.remove(this) | ||
|
||
if (textureId != -1) { | ||
GL11.glDeleteTextures(textureId) | ||
textureId = -1 | ||
} | ||
} | ||
|
||
companion object { | ||
val referenceQueue = ReferenceQueue<AlphaEffect>() | ||
val toBeCleanedUp: MutableSet<Resources> = Collections.newSetFromMap(ConcurrentHashMap()) | ||
|
||
fun drainCleanupQueue() { | ||
while (true) { | ||
((referenceQueue.poll() ?: break) as Resources).close() | ||
} | ||
} | ||
} | ||
} | ||
|
||
companion object { | ||
private lateinit var shader: UShader | ||
private lateinit var textureUniform: SamplerUniform | ||
|
||
private fun initShader() { | ||
if (::shader.isInitialized) return | ||
|
||
shader = UShader.fromLegacyShader(""" | ||
#version 110 | ||
varying vec2 f_Position; | ||
varying vec2 f_TexCoord; | ||
void main() { | ||
f_Position = gl_Vertex.xy; | ||
f_TexCoord = gl_MultiTexCoord0.st; | ||
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; | ||
gl_FrontColor = gl_Color; | ||
} | ||
""".trimIndent(), """ | ||
#version 110 | ||
uniform sampler2D u_Texture; | ||
varying vec2 f_Position; | ||
varying vec2 f_TexCoord; | ||
void main() { | ||
gl_FragColor = gl_Color * vec4(texture2D(u_Texture, f_TexCoord).rgb, 1.0); | ||
} | ||
""".trimIndent(), BlendState.NORMAL, UGraphics.CommonVertexFormats.POSITION_TEXTURE_COLOR) | ||
|
||
if (!shader.usable) { | ||
println("Failed to load AlphaEffect shader") | ||
return | ||
} | ||
|
||
textureUniform = shader.getSamplerUniform("u_Texture") | ||
} | ||
} | ||
} |
110 changes: 110 additions & 0 deletions
110
unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/effects/GradientEffect.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package gg.essential.elementa.effects | ||
|
||
import gg.essential.elementa.effects.Effect | ||
import gg.essential.elementa.state.v2.State | ||
import gg.essential.universal.UGraphics | ||
import gg.essential.universal.UMatrixStack | ||
import gg.essential.universal.shader.BlendState | ||
import gg.essential.universal.shader.UShader | ||
import org.intellij.lang.annotations.Language | ||
import org.lwjgl.opengl.GL11 | ||
import java.awt.Color | ||
|
||
/** | ||
* Draws a gradient (smooth color transition) behind the bound component. | ||
* | ||
* Unlike [gg.essential.elementa.components.GradientComponent], this effect also applies dithering to the gradient to | ||
* mitigate color banding artifacts. | ||
* | ||
* Note: The behavior of non-axis-aligned gradients (e.g. more than two colors, or diagonal) is currently undefined. | ||
*/ | ||
class GradientEffect( | ||
private val topLeft: State<Color>, | ||
private val topRight: State<Color>, | ||
private val bottomLeft: State<Color>, | ||
private val bottomRight: State<Color>, | ||
) : Effect() { | ||
override fun beforeChildrenDraw(matrixStack: UMatrixStack) { | ||
val topLeft = this.topLeft.get() | ||
val topRight = this.topRight.get() | ||
val bottomLeft = this.bottomLeft.get() | ||
val bottomRight = this.bottomRight.get() | ||
|
||
val dither = topLeft != topRight || topLeft != bottomLeft || bottomLeft != bottomRight | ||
if (dither) { | ||
shader.bind() | ||
} | ||
|
||
val buffer = UGraphics.getFromTessellator() | ||
if (dither) { | ||
buffer.beginWithActiveShader(UGraphics.DrawMode.QUADS, UGraphics.CommonVertexFormats.POSITION_COLOR) | ||
} else { | ||
buffer.beginWithDefaultShader(UGraphics.DrawMode.QUADS, UGraphics.CommonVertexFormats.POSITION_COLOR) | ||
} | ||
|
||
val x1 = boundComponent.getLeft().toDouble() | ||
val x2 = boundComponent.getRight().toDouble() | ||
val y1 = boundComponent.getTop().toDouble() | ||
val y2 = boundComponent.getBottom().toDouble() | ||
|
||
buffer.pos(matrixStack, x2, y1, 0.0).color(topRight).endVertex() | ||
buffer.pos(matrixStack, x1, y1, 0.0).color(topLeft).endVertex() | ||
buffer.pos(matrixStack, x1, y2, 0.0).color(bottomLeft).endVertex() | ||
buffer.pos(matrixStack, x2, y2, 0.0).color(bottomRight).endVertex() | ||
|
||
var prevAlphaTestFunc = 0 | ||
var prevAlphaTestRef = 0f | ||
if (!UGraphics.isCoreProfile()) { | ||
prevAlphaTestFunc = GL11.glGetInteger(GL11.GL_ALPHA_TEST_FUNC) | ||
prevAlphaTestRef = GL11.glGetFloat(GL11.GL_ALPHA_TEST_REF) | ||
UGraphics.alphaFunc(GL11.GL_ALWAYS, 0f) | ||
} | ||
|
||
// See UIBlock.drawBlock for why we use this depth function | ||
UGraphics.enableDepth() | ||
UGraphics.depthFunc(GL11.GL_ALWAYS) | ||
buffer.drawDirect() | ||
UGraphics.disableDepth() | ||
UGraphics.depthFunc(GL11.GL_LEQUAL) | ||
|
||
if (!UGraphics.isCoreProfile()) { | ||
UGraphics.alphaFunc(prevAlphaTestFunc, prevAlphaTestRef) | ||
} | ||
|
||
if (dither) { | ||
shader.unbind() | ||
} | ||
} | ||
|
||
companion object { | ||
@Language("GLSL") | ||
private val vertSource = """ | ||
varying vec4 vColor; | ||
void main() { | ||
gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex; | ||
vColor = gl_Color; | ||
} | ||
""".trimIndent() | ||
|
||
@Language("GLSL") | ||
private val fragSource = """ | ||
varying vec4 vColor; | ||
void main() { | ||
// Generate four pseudo-random values in range [-0.5; 0.5] for the current fragment coords, based on | ||
// Vlachos 2016, "Advanced VR Rendering" | ||
vec4 noise = vec4(dot(vec2(171.0, 231.0), gl_FragCoord.xy)); | ||
noise = fract(noise / vec4(103.0, 71.0, 97.0, 127.0)) - 0.5; | ||
// Apply dithering, i.e. randomly offset all the values within a color band, so there are no harsh | ||
// edges between different bands after quantization. | ||
gl_FragColor = vColor + noise / 255.0; | ||
} | ||
""".trimIndent() | ||
|
||
private val shader: UShader by lazy { | ||
UShader.fromLegacyShader(vertSource, fragSource, BlendState.NORMAL, UGraphics.CommonVertexFormats.POSITION_COLOR) | ||
} | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/layoutdsl/gradient.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package gg.essential.elementa.layoutdsl | ||
|
||
import gg.essential.elementa.effects.Effect | ||
import gg.essential.elementa.effects.GradientEffect | ||
import gg.essential.elementa.state.v2.State | ||
import gg.essential.elementa.state.v2.stateOf | ||
import java.awt.Color | ||
|
||
fun Modifier.gradient(top: Color, bottom: Color, _desc: GradientVertDesc = GradientDesc) = gradient(stateOf(top), stateOf(bottom), _desc) | ||
fun Modifier.gradient(left: Color, right: Color, _desc: GradientHorzDesc = GradientDesc) = gradient(stateOf(left), stateOf(right), _desc) | ||
|
||
fun Modifier.gradient(top: State<Color>, bottom: State<Color>, _desc: GradientVertDesc = GradientDesc) = gradient(top, top, bottom, bottom) | ||
fun Modifier.gradient(left: State<Color>, right: State<Color>, _desc: GradientHorzDesc = GradientDesc) = gradient(left, right, left, right) | ||
|
||
sealed interface GradientVertDesc | ||
sealed interface GradientHorzDesc | ||
private object GradientDesc : GradientVertDesc, GradientHorzDesc | ||
|
||
fun Modifier.gradient( | ||
topLeft: State<Color>, | ||
topRight: State<Color>, | ||
bottomLeft: State<Color>, | ||
bottomRight: State<Color>, | ||
) = effect { GradientEffect(topLeft, topRight, bottomLeft, bottomRight) } | ||
|
||
private fun Modifier.effect(effect: () -> Effect) = this then { | ||
val instance = effect() | ||
enableEffect(instance) | ||
return@then { | ||
removeEffect(instance) | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/transitions/FadeInTransition.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package gg.essential.elementa.transitions | ||
|
||
import gg.essential.elementa.constraints.animation.AnimatingConstraints | ||
import gg.essential.elementa.constraints.animation.Animations | ||
import gg.essential.elementa.state.BasicState | ||
import gg.essential.elementa.transitions.BoundTransition | ||
import gg.essential.elementa.effects.AlphaEffect | ||
import kotlin.properties.Delegates | ||
|
||
/** | ||
* Fades a component and all of its children in. This is done using | ||
* [AlphaEffect]. When the transition is finished, the effect is removed. | ||
*/ | ||
class FadeInTransition @JvmOverloads constructor( | ||
private val time: Float = 1f, | ||
private val animationType: Animations = Animations.OUT_EXP, | ||
) : BoundTransition() { | ||
|
||
private val alphaState = BasicState(0f) | ||
private var alpha by Delegates.observable(0f) { _, _, newValue -> | ||
alphaState.set(newValue) | ||
} | ||
|
||
private val effect = AlphaEffect(alphaState) | ||
|
||
override fun beforeTransition() { | ||
boundComponent.enableEffect(effect) | ||
} | ||
|
||
override fun doTransition(constraints: AnimatingConstraints) { | ||
constraints.setExtraDelay(time) | ||
boundComponent.apply { | ||
::alpha.animate(animationType, time, 1f) | ||
} | ||
} | ||
|
||
override fun afterTransition() { | ||
boundComponent.removeEffect(effect) | ||
effect.cleanup() | ||
} | ||
} |
Oops, something went wrong.