Shadows test

This commit is contained in:
DBotThePony 2022-09-14 00:49:50 +07:00
parent 3cc8d54ede
commit 0614158a9c
Signed by: DBot
GPG Key ID: DCC23B5715498507
31 changed files with 855 additions and 172 deletions

View File

@ -3,16 +3,24 @@ package ru.dbotthepony.kstarbound
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.lwjgl.Version import org.lwjgl.Version
import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose
import org.lwjgl.opengl.GL11.GL_LINES
import org.lwjgl.opengl.GL11.GL_RGBA
import org.lwjgl.opengl.GL46
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.BlendFunc
import ru.dbotthepony.kstarbound.client.gl.GLFrameBuffer
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kstarbound.client.gl.vertex.quad
import ru.dbotthepony.kstarbound.io.* import ru.dbotthepony.kstarbound.io.*
import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.ChunkPos
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
import ru.dbotthepony.kvector.vector.Color
import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.DataInputStream import java.io.DataInputStream
import java.io.File import java.io.File
import java.util.zip.Inflater import java.util.zip.Inflater
import kotlin.math.roundToInt
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
@ -28,9 +36,9 @@ fun main() {
Starbound.addPakPath(File("J:\\Steam\\steamapps\\common\\Starbound\\assets\\packed.pak")) Starbound.addPakPath(File("J:\\Steam\\steamapps\\common\\Starbound\\assets\\packed.pak"))
//Starbound.addPakPath(File("packed.pak")) //Starbound.addPakPath(File("packed.pak"))
Starbound.initializeGame { finished, replaceStatus, status -> /*Starbound.initializeGame { finished, replaceStatus, status ->
client.putDebugLog(status, replaceStatus) client.putDebugLog(status, replaceStatus)
} }*/
client.onTermination { client.onTermination {
Starbound.terminateLoading = true Starbound.terminateLoading = true
@ -163,13 +171,90 @@ fun main() {
//ent.position += Vector2d(y = 14.0, x = -10.0) //ent.position += Vector2d(y = 14.0, x = -10.0)
ent.position = Vector2d(600.0 + 16.0, 721.0 + 48.0) ent.position = Vector2d(600.0 + 16.0, 721.0 + 48.0)
client.camera.pos.x = 578f //client.camera.pos.x = 578f
client.camera.pos.y = 695f //client.camera.pos.y = 695f
client.onDrawGUI { client.onDrawGUI {
client.gl.font.render("${ent.position}", y = 100f, scale = 0.25f) client.gl.font.render("${ent.position}", y = 100f, scale = 0.25f)
client.gl.font.render("${ent.movement.velocity}", y = 120f, scale = 0.25f) client.gl.font.render("${ent.movement.velocity}", y = 120f, scale = 0.25f)
client.gl.font.render("${client.camera.pos}", y = 140f, scale = 0.25f) client.gl.font.render("${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f)
}
val lightmap = GLFrameBuffer(client.gl)
lightmap.reattachTexture(client.viewportWidth, client.viewportHeight, GL_RGBA)
client.onViewportChanged { width, height -> lightmap.reattachTexture(width, height, GL_RGBA) }
client.onPostDrawWorld {
lightmap.bind()
GL46.glClear(GL46.GL_COLOR_BUFFER_BIT or GL46.GL_DEPTH_BUFFER_BIT)
//client.gl.programs.colorQuad.clearAlpha()
client.gl.programs.light.use()
client.gl.programs.light.transform.set(client.gl.matrixStack.last)
// client.gl.programs.light.transform.set(Matrix4f.IDENTITY)
client.gl.programs.light.baselineColor.set(Color.WHITE)
var builder = client.gl.programs.light.builder
val (rX, rY) = client.screenToWorld(client.mouseCoordinatesF)
builder.begin()
//val size = 20f / client.settings.scale
val size = 10f
builder.quad(rX - size, rY - size, rX + size, rY + size, QuadTransformers.uv())
builder.upload()
//client.gl.blendFunc = BlendFunc(
// BlendFunc.Func.DST_ALPHA,
// BlendFunc.Func.ONE_MINUS_DST_ALPHA
//)
builder.draw()
client.gl.blendFunc = BlendFunc.ALPHA_TEST
client.gl.programs.lightOccluder.use()
client.gl.programs.lightOccluder.transform.set(client.gl.matrixStack.last)
builder = client.gl.programs.lightOccluder.builder
builder.begin()
val lightPosition = client.screenToWorld(client.mouseCoordinatesF)
builder.quad(0f, 0f, 2f, 2f)
builder.quad(-6f, 0f, -2f, 2f)
client.gl.programs.lightOccluder.lightPosition.set(lightPosition)
builder.upload()
client.gl.blendFunc = BlendFunc.ONLY_ALPHA
builder.draw(GL_LINES)
client.gl.blendFunc = BlendFunc.ALPHA_TEST
lightmap.unbind()
client.gl.activeTexture = 0
client.gl.texture2D = lightmap.texture
client.gl.programs.textureQuad.run(0)
client.gl.programs.flat.use()
client.gl.programs.flat.color.set(Color.BLACK)
client.gl.programs.flat.transform.set(client.gl.matrixStack.last)
builder = client.gl.programs.flat.builder
builder.begin()
builder.quad(0f, 0f, 2f, 2f)
builder.quad(-6f, 0f, -2f, 2f)
builder.upload()
builder.draw()
} }
client.onPreDrawWorld { client.onPreDrawWorld {
@ -186,9 +271,9 @@ fun main() {
client.input.addScrollCallback { _, x, y -> client.input.addScrollCallback { _, x, y ->
if (y > 0.0) { if (y > 0.0) {
client.settings.scale *= y.toFloat() * 2f client.settings.zoom *= y.toFloat() * 2f
} else if (y < 0.0) { } else if (y < 0.0) {
client.settings.scale /= -y.toFloat() * 2f client.settings.zoom /= -y.toFloat() * 2f
} }
} }
@ -199,11 +284,11 @@ fun main() {
//client.camera.pos.x = ent.position.x.toFloat() //client.camera.pos.x = ent.position.x.toFloat()
//client.camera.pos.y = ent.position.y.toFloat() //client.camera.pos.y = ent.position.y.toFloat()
client.camera.pos.x += if (client.input.KEY_LEFT_DOWN || client.input.KEY_A_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f client.camera.pos.x += if (client.input.KEY_LEFT_DOWN || client.input.KEY_A_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.zoom else 0f
client.camera.pos.x += if (client.input.KEY_RIGHT_DOWN || client.input.KEY_D_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f client.camera.pos.x += if (client.input.KEY_RIGHT_DOWN || client.input.KEY_D_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.zoom else 0f
client.camera.pos.y += if (client.input.KEY_UP_DOWN || client.input.KEY_W_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f client.camera.pos.y += if (client.input.KEY_UP_DOWN || client.input.KEY_W_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.zoom else 0f
client.camera.pos.y += if (client.input.KEY_DOWN_DOWN || client.input.KEY_S_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f client.camera.pos.y += if (client.input.KEY_DOWN_DOWN || client.input.KEY_S_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.zoom else 0f
//println(client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1) //println(client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1)

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.client
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.gl.vertex.quad
import ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh import ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh
import ru.dbotthepony.kstarbound.client.render.EntityRenderer import ru.dbotthepony.kstarbound.client.render.EntityRenderer
import ru.dbotthepony.kstarbound.client.render.ILayeredRenderer import ru.dbotthepony.kstarbound.client.render.ILayeredRenderer

View File

@ -6,7 +6,7 @@ data class ClientSettings(
* *
* Масштаб в единицу означает что один Starbound Unit будет равен 8 пикселям на экране * Масштаб в единицу означает что один Starbound Unit будет равен 8 пикселям на экране
*/ */
var scale: Float = 2f, var zoom: Float = 2f,
var debugCollisions: Boolean = false, var debugCollisions: Boolean = false,
) )

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.client
import org.lwjgl.opengl.GL46.* import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kstarbound.client.gl.vertex.quadZ
import ru.dbotthepony.kstarbound.client.render.ILayeredRenderer import ru.dbotthepony.kstarbound.client.render.ILayeredRenderer
import ru.dbotthepony.kstarbound.client.render.renderLayeredList import ru.dbotthepony.kstarbound.client.render.renderLayeredList
import ru.dbotthepony.kstarbound.defs.ParallaxPrototype import ru.dbotthepony.kstarbound.defs.ParallaxPrototype

View File

@ -10,16 +10,20 @@ import org.lwjgl.system.MemoryUtil
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.gl.BlendFunc
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.input.UserInput import ru.dbotthepony.kstarbound.client.input.UserInput
import ru.dbotthepony.kstarbound.client.render.Camera import ru.dbotthepony.kstarbound.client.render.Camera
import ru.dbotthepony.kstarbound.client.render.TextAlignX
import ru.dbotthepony.kstarbound.client.render.TextAlignY import ru.dbotthepony.kstarbound.client.render.TextAlignY
import ru.dbotthepony.kstarbound.util.formatBytesShort import ru.dbotthepony.kstarbound.util.formatBytesShort
import ru.dbotthepony.kvector.matrix.nfloat.Matrix4f import ru.dbotthepony.kvector.matrix.nfloat.Matrix4f
import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.Color import ru.dbotthepony.kvector.vector.Color
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import ru.dbotthepony.kvector.vector.nfloat.Vector2f
import ru.dbotthepony.kvector.vector.nfloat.Vector3f import ru.dbotthepony.kvector.vector.nfloat.Vector3f
import java.nio.ByteBuffer
import java.nio.ByteOrder
class StarboundClient : AutoCloseable { class StarboundClient : AutoCloseable {
val window: Long val window: Long
@ -35,10 +39,16 @@ class StarboundClient : AutoCloseable {
var viewportHeight = 600 var viewportHeight = 600
private set private set
var viewportMatrixGUI = updateViewportMatrixA() /**
* Матрица преобразования экранных координат (в пикселях) в нормализованные координаты
*/
var viewportMatrixScreen = updateViewportMatrixScreen()
private set private set
var viewportMatrixGame = updateViewportMatrixB() /**
* Матрица преобразования мировых координат в нормализованные координаты
*/
var viewportMatrixWorld = updateViewportMatrixWorld()
private set private set
private val startupTextList = ArrayList<String>() private val startupTextList = ArrayList<String>()
@ -58,12 +68,69 @@ class StarboundClient : AutoCloseable {
finishStartupRendering = System.currentTimeMillis() + 4000L finishStartupRendering = System.currentTimeMillis() + 4000L
} }
private fun updateViewportMatrixA(): Matrix4f { private fun updateViewportMatrixScreen(): Matrix4f {
return Matrix4f.ortho(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 0.1f, 100f) return Matrix4f.ortho(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 0.1f, 100f).translate(Vector3f(z = 2f)).toMatrix4f()
} }
private fun updateViewportMatrixB(): Matrix4f { private fun updateViewportMatrixWorld(): Matrix4f {
return Matrix4f.orthoDirect(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 1f, 100f) return Matrix4f.orthoDirect(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 1f, 100f).toMatrix4f()
}
private val xMousePos = ByteBuffer.allocateDirect(8).also { it.order(ByteOrder.LITTLE_ENDIAN) }.asDoubleBuffer()
private val yMousePos = ByteBuffer.allocateDirect(8).also { it.order(ByteOrder.LITTLE_ENDIAN) }.asDoubleBuffer()
val mouseCoordinates: Vector2d get() {
xMousePos.position(0)
yMousePos.position(0)
GLFW.glfwGetCursorPos(window, xMousePos, yMousePos)
xMousePos.position(0)
yMousePos.position(0)
return Vector2d(xMousePos.get(), yMousePos.get())
}
val mouseCoordinatesF: Vector2f get() {
xMousePos.position(0)
yMousePos.position(0)
GLFW.glfwGetCursorPos(window, xMousePos, yMousePos)
xMousePos.position(0)
yMousePos.position(0)
return Vector2f(xMousePos.get().toFloat(), yMousePos.get().toFloat())
}
/**
* Преобразует экранные координаты в мировые
*/
fun screenToWorld(x: Double, y: Double): Vector2d {
val relativeX = camera.pos.x - (viewportWidth / 2.0 - x) / PIXELS_IN_STARBOUND_UNIT / settings.zoom
val relativeY = (viewportHeight / 2.0 - y) / PIXELS_IN_STARBOUND_UNIT / settings.zoom + camera.pos.y
return Vector2d(relativeX, relativeY)
}
/**
* Преобразует экранные координаты в мировые
*/
fun screenToWorld(value: Vector2d): Vector2d {
return screenToWorld(value.x, value.y)
}
/**
* Преобразует экранные координаты в мировые
*/
fun screenToWorld(x: Float, y: Float): Vector2f {
val relativeX = camera.pos.x - (viewportWidth / 2f - x) / PIXELS_IN_STARBOUND_UNITf / settings.zoom
val relativeY = (viewportHeight / 2f - y) / PIXELS_IN_STARBOUND_UNITf / settings.zoom + camera.pos.y
return Vector2f(relativeX, relativeY)
}
/**
* Преобразует экранные координаты в мировые
*/
fun screenToWorld(value: Vector2f): Vector2f {
return screenToWorld(value.x, value.y)
} }
init { init {
@ -90,9 +157,13 @@ class StarboundClient : AutoCloseable {
GLFW.glfwSetFramebufferSizeCallback(window) { _, w, h -> GLFW.glfwSetFramebufferSizeCallback(window) { _, w, h ->
viewportWidth = w viewportWidth = w
viewportHeight = h viewportHeight = h
viewportMatrixGUI = updateViewportMatrixA() viewportMatrixScreen = updateViewportMatrixScreen()
viewportMatrixGame = updateViewportMatrixB() viewportMatrixWorld = updateViewportMatrixWorld()
glViewport(0, 0, w, h) glViewport(0, 0, w, h)
for (callback in onViewportChanged) {
callback.invoke(w, h)
}
} }
val stack = MemoryStack.stackPush() val stack = MemoryStack.stackPush()
@ -134,7 +205,7 @@ class StarboundClient : AutoCloseable {
gl.clearColor = Color.SLATE_GREY gl.clearColor = Color.SLATE_GREY
gl.blend = true gl.blend = true
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) gl.blendFunc = BlendFunc.ALPHA_TEST
} }
var frameRenderTime = 0.0 var frameRenderTime = 0.0
@ -160,6 +231,12 @@ class StarboundClient : AutoCloseable {
val settings = ClientSettings() val settings = ClientSettings()
private val onViewportChanged = ArrayList<(width: Int, height: Int) -> Unit>()
fun onViewportChanged(callback: (width: Int, height: Int) -> Unit) {
onViewportChanged.add(callback)
}
private val onDrawGUI = ArrayList<() -> Unit>() private val onDrawGUI = ArrayList<() -> Unit>()
fun onDrawGUI(lambda: () -> Unit) { fun onDrawGUI(lambda: () -> Unit) {
@ -198,11 +275,11 @@ class StarboundClient : AutoCloseable {
world?.think(frameRenderTime) world?.think(frameRenderTime)
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
gl.matrixStack.clear(viewportMatrixGame.toMutableMatrix4f()) gl.matrixStack.clear(viewportMatrixWorld.toMutableMatrix4f())
gl.matrixStack.push() gl.matrixStack.push()
.translateWithMultiplication(viewportWidth / 2f, viewportHeight / 2f, 2f) // центр экрана + координаты отрисовки мира .translateWithMultiplication(viewportWidth / 2f, viewportHeight / 2f, 2f) // центр экрана + координаты отрисовки мира
.scale(x = settings.scale * PIXELS_IN_STARBOUND_UNITf, y = settings.scale * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера .scale(x = settings.zoom * PIXELS_IN_STARBOUND_UNITf, y = settings.zoom * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера
.translateWithMultiplication(-camera.pos.x, -camera.pos.y) // перемещаем вид к камере .translateWithMultiplication(-camera.pos.x, -camera.pos.y) // перемещаем вид к камере
for (lambda in onPreDrawWorld) { for (lambda in onPreDrawWorld) {
@ -217,8 +294,8 @@ class StarboundClient : AutoCloseable {
world?.render( world?.render(
AABB.rectangle( AABB.rectangle(
camera.pos.toDoubleVector(), camera.pos.toDoubleVector(),
viewportWidth / settings.scale / PIXELS_IN_STARBOUND_UNIT, viewportWidth / settings.zoom / PIXELS_IN_STARBOUND_UNIT,
viewportHeight / settings.scale / PIXELS_IN_STARBOUND_UNIT)) viewportHeight / settings.zoom / PIXELS_IN_STARBOUND_UNIT))
for (lambda in onPostDrawWorld) { for (lambda in onPostDrawWorld) {
lambda.invoke() lambda.invoke()
@ -226,7 +303,7 @@ class StarboundClient : AutoCloseable {
gl.matrixStack.pop() gl.matrixStack.pop()
gl.matrixStack.clear(viewportMatrixGUI.toMutableMatrix4f().translate(Vector3f(z = 2f))) gl.matrixStack.clear(viewportMatrixScreen.toMutableMatrix4f())
val thisTime = System.currentTimeMillis() val thisTime = System.currentTimeMillis()

View File

@ -0,0 +1,93 @@
package ru.dbotthepony.kstarbound.client.gl
import org.lwjgl.opengl.GL30.GL_COLOR_ATTACHMENT0
import org.lwjgl.opengl.GL30.GL_FRAMEBUFFER
import org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_COMPLETE
import org.lwjgl.opengl.GL30.GL_LINEAR
import org.lwjgl.opengl.GL30.GL_RGB
import org.lwjgl.opengl.GL30.GL_TEXTURE
import org.lwjgl.opengl.GL30.GL_TEXTURE_2D
import org.lwjgl.opengl.GL30.glCheckFramebufferStatus
import org.lwjgl.opengl.GL30.glFramebufferTexture2D
import org.lwjgl.opengl.GL45.glCheckNamedFramebufferStatus
import org.lwjgl.opengl.GL45.glNamedFramebufferTexture
import org.lwjgl.opengl.GL46
class GLFrameBuffer(val state: GLStateTracker) : AutoCloseable {
init {
state.ensureSameThread()
}
val pointer = GL46.glGenFramebuffers()
init {
checkForGLError()
}
val isComplete: Boolean get() {
state.ensureSameThread()
return glCheckNamedFramebufferStatus(pointer, GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE
}
var texture: GLTexture2D? = null
private set
fun attachTexture(width: Int, height: Int, format: Int = GL_RGB) {
if (texture != null) {
throw IllegalStateException("Already has texture attached")
}
val texture = GLTexture2D(state, "framebuffer_$pointer")
texture.allocate(format, width, height)
val old = state.framebuffer
bind()
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.pointer, 0)
checkForGLError()
this.texture = texture
texture.textureMinFilter = GL_LINEAR
state.framebuffer = old
}
fun reattachTexture(width: Int, height: Int, format: Int = GL_RGB) {
texture?.close()
texture = null
attachTexture(width, height, format)
}
fun bind() {
state.framebuffer = this
}
fun bindWrite() {
state.writeFramebuffer = this
}
fun bindRead() {
state.readFramebuffer = this
}
fun unbind() {
if (state.writeFramebuffer == this) {
state.writeFramebuffer = null
}
if (state.readFramebuffer == this) {
state.readFramebuffer = null
}
}
private val cleanable = state.registerCleanable(this, GL46::glDeleteFramebuffers, "Framebuffer", pointer)
var isValid = true
private set
override fun close() {
state.ensureSameThread()
if (!isValid)
return
cleanable.cleanManual()
isValid = false
}
}

View File

@ -2,9 +2,11 @@ package ru.dbotthepony.kstarbound.client.gl
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.lwjgl.opengl.GL import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL14
import org.lwjgl.opengl.GL46.* import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.freetype.FreeType import ru.dbotthepony.kstarbound.client.freetype.FreeType
import ru.dbotthepony.kstarbound.client.freetype.InvalidArgumentException
import ru.dbotthepony.kstarbound.client.gl.program.GLPrograms import ru.dbotthepony.kstarbound.client.gl.program.GLPrograms
import ru.dbotthepony.kstarbound.client.gl.shader.GLShader import ru.dbotthepony.kstarbound.client.gl.shader.GLShader
import ru.dbotthepony.kstarbound.client.gl.program.GLShaderProgram import ru.dbotthepony.kstarbound.client.gl.program.GLShaderProgram
@ -12,7 +14,8 @@ import ru.dbotthepony.kstarbound.client.gl.shader.GLTransformableColorableProgra
import ru.dbotthepony.kstarbound.client.gl.shader.GLTransformableProgram import ru.dbotthepony.kstarbound.client.gl.shader.GLTransformableProgram
import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexType import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
import ru.dbotthepony.kstarbound.client.gl.vertex.quad
import ru.dbotthepony.kstarbound.client.render.Box2DRenderer import ru.dbotthepony.kstarbound.client.render.Box2DRenderer
import ru.dbotthepony.kstarbound.client.render.Font import ru.dbotthepony.kstarbound.client.render.Font
import ru.dbotthepony.kstarbound.client.render.TileRenderers import ru.dbotthepony.kstarbound.client.render.TileRenderers
@ -22,7 +25,10 @@ import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.Color import ru.dbotthepony.kvector.vector.Color
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.lang.ref.Cleaner import java.lang.ref.Cleaner
import java.util.*
import java.util.concurrent.ThreadFactory import java.util.concurrent.ThreadFactory
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
private class GLStateSwitchTracker(private val enum: Int, private var value: Boolean = false) { private class GLStateSwitchTracker(private val enum: Int, private var value: Boolean = false) {
@ -47,7 +53,7 @@ private class GLStateSwitchTracker(private val enum: Int, private var value: Boo
} }
} }
private class GLStateGenericTracker<T>(private var value: T, private val lambda: (T) -> Unit) { private class GLStateGenericTracker<T>(private var value: T, private val callback: (T) -> Unit) {
operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): T { operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): T {
return value return value
} }
@ -58,12 +64,69 @@ private class GLStateGenericTracker<T>(private var value: T, private val lambda:
if (value == this.value) if (value == this.value)
return return
lambda.invoke(value) callback.invoke(value)
checkForGLError() checkForGLError()
this.value = value this.value = value
} }
} }
@Suppress("unused")
data class BlendFunc(
val sourceColor: Func,
val destinationColor: Func,
val sourceAlpha: Func,
val destinationAlpha: Func
) {
constructor() : this(
Func.ONE,
Func.ZERO,
Func.ONE,
Func.ZERO
)
constructor(
source: Func, destination: Func
) : this(
source,
destination,
source,
destination
)
enum class Func(val enum: Int) {
ZERO(GL_ZERO),
ONE(GL_ONE),
SRC_COLOR(GL_SRC_COLOR),
ONE_MINUS_SRC_COLOR(GL_ONE_MINUS_SRC_COLOR),
SRC_ALPHA(GL_SRC_ALPHA),
ONE_MINUS_SRC_ALPHA(GL_ONE_MINUS_SRC_ALPHA),
DST_ALPHA(GL_DST_ALPHA),
ONE_MINUS_DST_ALPHA(GL_ONE_MINUS_DST_ALPHA),
DST_COLOR(GL_DST_COLOR),
ONE_MINUS_DST_COLOR(GL_ONE_MINUS_DST_COLOR),
SRC_ALPHA_SATURATE(GL_SRC_ALPHA_SATURATE);
}
companion object {
val DEFAULT = BlendFunc()
val ALPHA_TEST = BlendFunc(Func.SRC_ALPHA, Func.ONE_MINUS_SRC_ALPHA)
val ONLY_ALPHA = BlendFunc(
Func.ZERO,
Func.ONE,
Func.ONE,
Func.ZERO
)
val ONLY_COLOR = BlendFunc(
Func.ONE,
Func.ZERO,
Func.ZERO,
Func.ONE
)
}
}
interface GLCleanable : Cleaner.Cleanable { interface GLCleanable : Cleaner.Cleanable {
/** /**
* Выставляет флаг на то, что объект был удалён вручную и вызывает clean() * Выставляет флаг на то, что объект был удалён вручную и вызывает clean()
@ -75,7 +138,14 @@ interface GLStreamBuilderList {
val small: StreamVertexBuilder val small: StreamVertexBuilder
} }
@Suppress("PropertyName", "unused")
class GLStateTracker { class GLStateTracker {
private fun isMe(state: GLStateTracker?) {
if (state != null && state != this) {
throw InvalidArgumentException("Provided object does not belong to $this state tracker (belongs to $state)")
}
}
init { init {
check(TRACKERS.get() == null) { "Already has state tracker existing at this thread!" } check(TRACKERS.get() == null) { "Already has state tracker existing at this thread!" }
TRACKERS.set(this) TRACKERS.set(this)
@ -87,7 +157,8 @@ class GLStateTracker {
GL.createCapabilities() GL.createCapabilities()
} }
private var cleanerHits = ArrayList<() -> Unit>() private val cleanerHits = ArrayList<() -> Unit>()
private val cleaner = Cleaner.create(object : ThreadFactory { private val cleaner = Cleaner.create(object : ThreadFactory {
override fun newThread(r: Runnable): Thread { override fun newThread(r: Runnable): Thread {
val thread = Thread(r, "OpenGL Object Cleaner@" + System.identityHashCode(this)) val thread = Thread(r, "OpenGL Object Cleaner@" + System.identityHashCode(this))
@ -101,11 +172,13 @@ class GLStateTracker {
val cleanable = cleaner.register(ref) { val cleanable = cleaner.register(ref) {
if (!cleanManual) if (!cleanManual)
LOGGER.error("{} with ID {} got leaked.", name, nativeRef) LOGGER.error("{} with ID {} was GC'd by JVM, but it should have been removed manually.", name, nativeRef)
cleanerHits.add { synchronized(cleanerHits) {
fn(nativeRef) cleanerHits.add {
checkForGLError() fn(nativeRef)
checkForGLError()
}
} }
} }
@ -120,11 +193,12 @@ class GLStateTracker {
} }
fun cleanup() { fun cleanup() {
val copy = cleanerHits synchronized(cleanerHits) {
cleanerHits = ArrayList() for (lambda in cleanerHits) {
lambda.invoke()
}
for (lambda in copy) { cleanerHits.clear()
lambda.invoke()
} }
} }
@ -135,6 +209,7 @@ class GLStateTracker {
set(value) { set(value) {
ensureSameThread() ensureSameThread()
if (field === value) return if (field === value) return
isMe(value?.state)
field = value field = value
if (value == null) { if (value == null) {
@ -152,6 +227,7 @@ class GLStateTracker {
set(value) { set(value) {
ensureSameThread() ensureSameThread()
if (field === value) return if (field === value) return
isMe(value?.state)
field = value field = value
if (value == null) { if (value == null) {
@ -169,6 +245,7 @@ class GLStateTracker {
set(value) { set(value) {
ensureSameThread() ensureSameThread()
if (field === value) return if (field === value) return
isMe(value?.state)
field = value field = value
if (value == null) { if (value == null) {
@ -181,6 +258,56 @@ class GLStateTracker {
checkForGLError() checkForGLError()
} }
var readFramebuffer: GLFrameBuffer? = null
set(value) {
ensureSameThread()
if (field === value) return
isMe(value?.state)
field = value
if (value == null) {
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0)
checkForGLError()
return
}
glBindFramebuffer(GL_READ_FRAMEBUFFER, value.pointer)
checkForGLError()
}
var writeFramebuffer: GLFrameBuffer? = null
set(value) {
ensureSameThread()
if (field === value) return
isMe(value?.state)
field = value
if (value == null) {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)
checkForGLError()
return
}
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, value.pointer)
checkForGLError()
}
var framebuffer: GLFrameBuffer?
get() {
val readFramebuffer = readFramebuffer
val writeFramebuffer = writeFramebuffer
if (readFramebuffer == writeFramebuffer) {
return writeFramebuffer
}
return null
}
set(value) {
readFramebuffer = value
writeFramebuffer = value
}
var program: GLShaderProgram? = null var program: GLShaderProgram? = null
private set private set
@ -188,6 +315,7 @@ class GLStateTracker {
set(value) { set(value) {
ensureSameThread() ensureSameThread()
if (field === value) return if (field === value) return
isMe(value?.state)
field = value field = value
if (value == null) return if (value == null) return
glBindTexture(GL_TEXTURE_2D, value.pointer) glBindTexture(GL_TEXTURE_2D, value.pointer)
@ -210,6 +338,10 @@ class GLStateTracker {
glClearColor(r, g, b, a) glClearColor(r, g, b, a)
} }
var blendFunc by GLStateGenericTracker(BlendFunc()) {
glBlendFuncSeparate(it.sourceColor.enum, it.destinationColor.enum, it.sourceAlpha.enum, it.destinationAlpha.enum)
}
init { init {
glActiveTexture(GL_TEXTURE0) glActiveTexture(GL_TEXTURE0)
checkForGLError() checkForGLError()
@ -362,42 +494,42 @@ class GLStateTracker {
val flat2DLines = object : GLStreamBuilderList { val flat2DLines = object : GLStreamBuilderList {
override val small by lazy { override val small by lazy {
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, VertexType.LINES, 1024) return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, GeometryType.LINES, 1024)
} }
} }
val flat2DTriangles = object : GLStreamBuilderList { val flat2DTriangles = object : GLStreamBuilderList {
override val small by lazy { override val small by lazy {
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, VertexType.TRIANGLES, 1024) return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, GeometryType.TRIANGLES, 1024)
} }
} }
val flat2DQuads = object : GLStreamBuilderList { val flat2DQuads = object : GLStreamBuilderList {
override val small by lazy { override val small by lazy {
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, VertexType.QUADS, 1024) return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, GeometryType.QUADS, 1024)
} }
} }
val flat2DTexturedQuads = object : GLStreamBuilderList { val flat2DTexturedQuads = object : GLStreamBuilderList {
override val small by lazy { override val small by lazy {
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VERTEX_TEXTURE, VertexType.QUADS, 1024) return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VERTEX_TEXTURE, GeometryType.QUADS, 1024)
} }
} }
val flat2DQuadLines = object : GLStreamBuilderList { val flat2DQuadLines = object : GLStreamBuilderList {
override val small by lazy { override val small by lazy {
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, VertexType.QUADS_AS_LINES, 1024) return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, GeometryType.QUADS_AS_LINES, 1024)
} }
} }
val flat2DQuadWireframe = object : GLStreamBuilderList { val flat2DQuadWireframe = object : GLStreamBuilderList {
override val small by lazy { override val small by lazy {
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, VertexType.QUADS_AS_LINES_WIREFRAME, 1024) return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, GeometryType.QUADS_AS_LINES_WIREFRAME, 1024)
} }
} }
val quadWireframe by lazy { val quadWireframe by lazy {
StreamVertexBuilder(this, GLAttributeList.VEC2F, VertexType.QUADS_AS_LINES_WIREFRAME, 16384) StreamVertexBuilder(this, GLAttributeList.VEC2F, GeometryType.QUADS_AS_LINES_WIREFRAME, 16384)
} }
val matrixStack = Matrix4fStack() val matrixStack = Matrix4fStack()

View File

@ -27,6 +27,7 @@ class GLTexturePropertyTracker(private val flag: Int, var value: Int) {
} }
} }
@Suppress("SameParameterValue")
class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : AutoCloseable { class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : AutoCloseable {
val pointer = glGenTextures() val pointer = glGenTextures()
@ -63,18 +64,20 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : A
private var mipsWarning = 2 private var mipsWarning = 2
var textureMinFilter by GLTexturePropertyTracker(GL_TEXTURE_MIN_FILTER, GL_LINEAR) var textureMinFilter by GLTexturePropertyTracker(GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR)
var textureMagFilter by GLTexturePropertyTracker(GL_TEXTURE_MAG_FILTER, GL_LINEAR) var textureMagFilter by GLTexturePropertyTracker(GL_TEXTURE_MAG_FILTER, GL_LINEAR)
var textureWrapS by GLTexturePropertyTracker(GL_TEXTURE_WRAP_S, GL_REPEAT) var textureWrapS by GLTexturePropertyTracker(GL_TEXTURE_WRAP_S, GL_REPEAT)
var textureWrapT by GLTexturePropertyTracker(GL_TEXTURE_WRAP_T, GL_REPEAT) var textureWrapT by GLTexturePropertyTracker(GL_TEXTURE_WRAP_T, GL_REPEAT)
fun bind(): GLTexture2D { fun bind(): GLTexture2D {
if (mipsWarning == 1) { if (textureMinFilter != GL_LINEAR && textureMinFilter != GL_NEAREST) {
LOGGER.warn("(Likely) Trying to use texture {} before generated it's mips, this probably won't work!", this) if (mipsWarning == 1) {
mipsWarning = 0 LOGGER.warn("(Likely) Trying to use texture {} before generated it's mips, this probably won't work!", this)
} else if (mipsWarning == 2) { mipsWarning = 0
mipsWarning = 1 } else if (mipsWarning == 2) {
mipsWarning = 1
}
} }
state.texture2D = this state.texture2D = this
@ -101,6 +104,23 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : A
fun pixelToUV(pos: Vector2i) = pixelToUV(pos.x, pos.y) fun pixelToUV(pos: Vector2i) = pixelToUV(pos.x, pos.y)
fun allocate(mipmap: Int, loadedFormat: Int, width: Int, height: Int): GLTexture2D {
bind()
require(width > 0) { "Invalid width $width" }
require(height > 0) { "Invalid height $height" }
this.width = width
this.height = height
glTexImage2D(GL_TEXTURE_2D, mipmap, loadedFormat, width, height, 0, loadedFormat, GL_UNSIGNED_BYTE, 0L)
checkForGLError()
uploaded = true
return this
}
fun allocate(loadedFormat: Int, width: Int, height: Int): GLTexture2D {
return allocate(0, loadedFormat, width, height)
}
private fun upload(mipmap: Int, loadedFormat: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: IntArray): GLTexture2D { private fun upload(mipmap: Int, loadedFormat: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: IntArray): GLTexture2D {
bind() bind()

View File

@ -5,9 +5,10 @@ import ru.dbotthepony.kstarbound.client.gl.shader.GLShader
private fun loadShaders( private fun loadShaders(
vertex: Collection<String>, vertex: Collection<String>,
fragment: Collection<String> fragment: Collection<String>,
geometry: Collection<String>
): Array<GLShader> { ): Array<GLShader> {
val result = arrayOfNulls<GLShader>(vertex.size + fragment.size) val result = arrayOfNulls<GLShader>(vertex.size + fragment.size + geometry.size)
var i = 0 var i = 0
for (name in vertex) { for (name in vertex) {
@ -18,13 +19,18 @@ private fun loadShaders(
result[i++] = GLShader.internalFragment("shaders/$name.fsh") result[i++] = GLShader.internalFragment("shaders/$name.fsh")
} }
for (name in geometry) {
result[i++] = GLShader.internalGeometry("shaders/$name.gsh")
}
return result as Array<GLShader> return result as Array<GLShader>
} }
open class GLInternalProgram( open class GLInternalProgram(
state: GLStateTracker, state: GLStateTracker,
vertex: Collection<String>, vertex: Collection<String>,
fragment: Collection<String> fragment: Collection<String>,
) : GLShaderProgram(state, *loadShaders(vertex, fragment)) { geometry: Collection<String> = listOf()
) : GLShaderProgram(state, *loadShaders(vertex, fragment, geometry)) {
constructor(state: GLStateTracker, name: String) : this(state, listOf(name), listOf(name)) constructor(state: GLStateTracker, name: String) : this(state, listOf(name), listOf(name))
} }

View File

@ -75,7 +75,7 @@ open class GLShaderProgram(val state: GLStateTracker, vararg shaders: GLShader)
glGetProgramiv(pointer, GL_LINK_STATUS, success) glGetProgramiv(pointer, GL_LINK_STATUS, success)
if (success[0] == 0) { if (success[0] == 0) {
throw ShaderLinkException(glGetShaderInfoLog(pointer)) throw ShaderLinkException(glGetProgramInfoLog(pointer))
} }
glGetError() glGetError()

View File

@ -1,13 +1,16 @@
package ru.dbotthepony.kstarbound.client.gl.program package ru.dbotthepony.kstarbound.client.gl.program
import ru.dbotthepony.kstarbound.client.gl.BlendFunc
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.gl.GLType import ru.dbotthepony.kstarbound.client.gl.GLType
import ru.dbotthepony.kstarbound.client.gl.shader.GLShader import ru.dbotthepony.kstarbound.client.gl.shader.GLShader
import ru.dbotthepony.kstarbound.client.gl.shader.GLTransformableColorableProgram import ru.dbotthepony.kstarbound.client.gl.shader.GLTransformableColorableProgram
import ru.dbotthepony.kstarbound.client.gl.shader.GLTransformableProgram
import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexType import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kstarbound.client.gl.vertex.quad
import ru.dbotthepony.kvector.vector.Color
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
@ -50,7 +53,130 @@ class GLLiquidProgram(state: GLStateTracker) : GLInternalProgram(state, "liquid"
val transform = this["transform"]!! val transform = this["transform"]!!
val builder by lazy { val builder by lazy {
StreamVertexBuilder(state, FORMAT, VertexType.QUADS, 16384) StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 16384)
}
companion object {
val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).build()
}
}
class GLLightProgram(state: GLStateTracker) : GLInternalProgram(state, "light") {
init {
link()
}
val baselineColor = this["baselineColor"]!!
val transform = this["transform"]!!
val builder by lazy {
StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 32)
}
companion object {
val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).push(GLType.VEC2F).build()
}
}
class GLLightOccluderProgram(state: GLStateTracker) : GLInternalProgram(state, listOf("light_occluder"), listOf("light_occluder"), listOf("light_occluder")) {
init {
link()
}
// val baselineColor = this["baselineColor"]!!
val transform = this["transform"]!!
val lightPosition = this["lightPosition"]!!
val builder by lazy {
StreamVertexBuilder(state, FORMAT, GeometryType.QUADS_AS_LINES, 32)
}
companion object {
val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).build()
}
}
class GLColorQuadProgram(state: GLStateTracker) : GLInternalProgram(state, "screen_quad") {
init {
link()
}
val color = this["color"]!!
private val builder by lazy {
val builder = StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 1)
builder.begin()
builder.quad(-1f, -1f, 1f, 1f)
builder.upload()
builder
}
fun clearAlpha() {
use()
color.set(ALPHA)
val old = state.blend
val oldFunc = state.blendFunc
state.blend = true
state.blendFunc = BlendFunc.ONLY_ALPHA
builder.draw()
state.blend = old
state.blendFunc = oldFunc
}
fun clearColor(color: Color = Color.WHITE) {
use()
this.color.set(color)
val old = state.blend
state.blend = false
builder.draw()
state.blend = old
}
companion object {
val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).build()
val ALPHA = Color(0f, 0f, 0f, 1f)
}
}
class GLTextureQuadProgram(state: GLStateTracker) : GLInternalProgram(state, "screen_quad_tex") {
init {
link()
}
val texture = this["texture0"]!!
private val builder by lazy {
val builder = StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 1)
builder.begin()
builder.quad(-1f, -1f, 1f, 1f, QuadTransformers.uv())
builder.upload()
builder
}
fun run(texture: Int = 0) {
use()
this.texture.set(texture)
builder.draw()
}
companion object {
val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).push(GLType.VEC2F).build()
}
}
class GLFlatProgram(state: GLStateTracker, vararg shaders: GLShader) : GLTransformableColorableProgram(state, *shaders) {
val builder by lazy {
StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 1024)
} }
companion object { companion object {
@ -61,6 +187,11 @@ class GLLiquidProgram(state: GLStateTracker) : GLInternalProgram(state, "liquid"
class GLPrograms(val state: GLStateTracker) { class GLPrograms(val state: GLStateTracker) {
val tile by SimpleProgram("tile", ::GLTransformableColorableProgram) val tile by SimpleProgram("tile", ::GLTransformableColorableProgram)
val font by SimpleProgram("font", ::GLTransformableColorableProgram) val font by SimpleProgram("font", ::GLTransformableColorableProgram)
val flat by SimpleProgram("flat", ::GLTransformableColorableProgram) val flat by SimpleProgram("flat", ::GLFlatProgram)
val liquid by lazy { GLLiquidProgram(state) } val liquid by lazy { GLLiquidProgram(state) }
val light by lazy { GLLightProgram(state) }
val lightOccluder by lazy { GLLightOccluderProgram(state) }
val colorQuad by lazy { GLColorQuadProgram(state) }
val textureQuad by lazy { GLTextureQuadProgram(state) }
} }

View File

@ -56,5 +56,6 @@ class GLShader(
fun internalVertex(file: String) = GLShader(readInternal(file), GL_VERTEX_SHADER) fun internalVertex(file: String) = GLShader(readInternal(file), GL_VERTEX_SHADER)
fun internalFragment(file: String) = GLShader(readInternal(file), GL_FRAGMENT_SHADER) fun internalFragment(file: String) = GLShader(readInternal(file), GL_FRAGMENT_SHADER)
fun internalGeometry(file: String) = GLShader(readInternal(file), GL_GEOMETRY_SHADER)
} }
} }

View File

@ -4,6 +4,7 @@ import org.lwjgl.opengl.GL41.*
import ru.dbotthepony.kstarbound.client.gl.checkForGLError import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import ru.dbotthepony.kstarbound.client.gl.program.GLShaderProgram import ru.dbotthepony.kstarbound.client.gl.program.GLShaderProgram
import ru.dbotthepony.kvector.api.IFloatMatrix import ru.dbotthepony.kvector.api.IFloatMatrix
import ru.dbotthepony.kvector.api.IStruct2f
import ru.dbotthepony.kvector.api.IStruct3f import ru.dbotthepony.kvector.api.IStruct3f
import ru.dbotthepony.kvector.api.IStruct4f import ru.dbotthepony.kvector.api.IStruct4f
import java.nio.ByteBuffer import java.nio.ByteBuffer
@ -19,6 +20,14 @@ class GLUniformLocation(val program: GLShaderProgram, val name: String, val poin
return this return this
} }
fun set(value: IStruct2f): GLUniformLocation {
program.state.ensureSameThread()
val (v0, v1) = value
glProgramUniform2f(program.pointer, pointer, v0, v1)
checkForGLError()
return this
}
fun set(value: IStruct3f): GLUniformLocation { fun set(value: IStruct3f): GLUniformLocation {
program.state.ensureSameThread() program.state.ensureSameThread()
val (v0, v1, v2) = value val (v0, v1, v2) = value

View File

@ -26,9 +26,9 @@ private fun put(type: Int, memory: OutputStream, value: Int) {
* Класс для построения геометрии для загрузки в память видеокарты. * Класс для построения геометрии для загрузки в память видеокарты.
*/ */
@Suppress("unchecked_cast") @Suppress("unchecked_cast")
abstract class AbstractVertexBuilder<T : AbstractVertexBuilder<T>>( abstract class AbstractVertexBuilder<out T : AbstractVertexBuilder<T>>(
val attributes: GLAttributeList, val attributes: GLAttributeList,
val type: VertexType, val type: GeometryType,
) { ) {
protected abstract val vertexMemory: OutputStream protected abstract val vertexMemory: OutputStream
protected abstract val elementMemory: OutputStream protected abstract val elementMemory: OutputStream
@ -165,97 +165,98 @@ abstract class AbstractVertexBuilder<T : AbstractVertexBuilder<T>>(
attributeIndex++ attributeIndex++
return this as T return this as T
} }
}
// Помощники
fun quad(
x0: Float, // Помощники
y0: Float, fun <T : AbstractVertexBuilder<T>> T.quad(
x1: Float, x0: Float,
y1: Float, y0: Float,
lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM x1: Float,
): T { y1: Float,
check(type.elements == 4) { "Currently building $type" } lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM
): T {
lambda(vertex().pushVec2f(x0, y0), 0).end() check(type.elements == 4) { "Currently building $type" }
lambda(vertex().pushVec2f(x1, y0), 1).end()
lambda(vertex().pushVec2f(x0, y1), 2).end() lambda(vertex().pushVec2f(x0, y0), 0).end()
lambda(vertex().pushVec2f(x1, y1), 3).end() lambda(vertex().pushVec2f(x1, y0), 1).end()
lambda(vertex().pushVec2f(x0, y1), 2).end()
return this as T lambda(vertex().pushVec2f(x1, y1), 3).end()
}
return this
fun quadRotated( }
x0: Float,
y0: Float, fun <T : AbstractVertexBuilder<T>> T.quadRotated(
x1: Float, x0: Float,
y1: Float, y0: Float,
x: Float, x1: Float,
y: Float, y1: Float,
angle: Double, x: Float,
lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM y: Float,
): T { angle: Double,
check(type.elements == 4) { "Currently building $type" } lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM
): T {
val s = sin(angle).toFloat() check(type.elements == 4) { "Currently building $type" }
val c = cos(angle).toFloat()
val s = sin(angle).toFloat()
lambda(vertex().pushVec2f(x + x0 * c - s * y0, y + s * x0 + c * y0), 0).end() val c = cos(angle).toFloat()
lambda(vertex().pushVec2f(x + x1 * c - s * y0, y + s * x1 + c * y0), 1).end()
lambda(vertex().pushVec2f(x + x0 * c - s * y1, y + s * x0 + c * y1), 2).end() lambda(vertex().pushVec2f(x + x0 * c - s * y0, y + s * x0 + c * y0), 0).end()
lambda(vertex().pushVec2f(x + x1 * c - s * y1, y + s * x1 + c * y1), 3).end() lambda(vertex().pushVec2f(x + x1 * c - s * y0, y + s * x1 + c * y0), 1).end()
lambda(vertex().pushVec2f(x + x0 * c - s * y1, y + s * x0 + c * y1), 2).end()
return this as T lambda(vertex().pushVec2f(x + x1 * c - s * y1, y + s * x1 + c * y1), 3).end()
}
return this
fun quad(aabb: AABB, lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM): T { }
return quad(
aabb.mins.x.toFloat(), fun <T : AbstractVertexBuilder<T>> T.quad(aabb: AABB, lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM): T {
aabb.mins.y.toFloat(), return quad(
aabb.maxs.x.toFloat(), aabb.mins.x.toFloat(),
aabb.maxs.y.toFloat(), aabb.mins.y.toFloat(),
lambda aabb.maxs.x.toFloat(),
) aabb.maxs.y.toFloat(),
} lambda
)
fun quadZ( }
x0: Float,
y0: Float, fun <T : AbstractVertexBuilder<T>> T.quadZ(
x1: Float, x0: Float,
y1: Float, y0: Float,
z: Float, x1: Float,
lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM y1: Float,
): T { z: Float,
check(type.elements == 4) { "Currently building $type" } lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM
): T {
lambda(vertex().pushVec3f(x0, y0, z), 0).end() check(type.elements == 4) { "Currently building $type" }
lambda(vertex().pushVec3f(x1, y0, z), 1).end()
lambda(vertex().pushVec3f(x0, y1, z), 2).end() lambda(vertex().pushVec3f(x0, y0, z), 0).end()
lambda(vertex().pushVec3f(x1, y1, z), 3).end() lambda(vertex().pushVec3f(x1, y0, z), 1).end()
lambda(vertex().pushVec3f(x0, y1, z), 2).end()
return this as T lambda(vertex().pushVec3f(x1, y1, z), 3).end()
}
return this
fun quadRotatedZ( }
x0: Float,
y0: Float, fun <T : AbstractVertexBuilder<T>> T.quadRotatedZ(
x1: Float, x0: Float,
y1: Float, y0: Float,
z: Float, x1: Float,
x: Float, y1: Float,
y: Float, z: Float,
angle: Double, x: Float,
lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM y: Float,
): T { angle: Double,
check(type.elements == 4) { "Currently building $type" } lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM
): T {
val s = sin(angle).toFloat() check(type.elements == 4) { "Currently building $type" }
val c = cos(angle).toFloat()
val s = sin(angle).toFloat()
lambda(vertex().pushVec3f(x + x0 * c - s * y0, y + s * x0 + c * y0, z), 0).end() val c = cos(angle).toFloat()
lambda(vertex().pushVec3f(x + x1 * c - s * y0, y + s * x1 + c * y0, z), 1).end()
lambda(vertex().pushVec3f(x + x0 * c - s * y1, y + s * x0 + c * y1, z), 2).end() lambda(vertex().pushVec3f(x + x0 * c - s * y0, y + s * x0 + c * y0, z), 0).end()
lambda(vertex().pushVec3f(x + x1 * c - s * y1, y + s * x1 + c * y1, z), 3).end() lambda(vertex().pushVec3f(x + x1 * c - s * y0, y + s * x1 + c * y0, z), 1).end()
lambda(vertex().pushVec3f(x + x0 * c - s * y1, y + s * x0 + c * y1, z), 2).end()
return this as T lambda(vertex().pushVec3f(x + x1 * c - s * y1, y + s * x1 + c * y1, z), 3).end()
}
return this
} }

View File

@ -6,24 +6,24 @@ import ru.dbotthepony.kstarbound.util.ByteBufferOutputStream
open class DirectVertexBuilder<T : DirectVertexBuilder<T>>( open class DirectVertexBuilder<T : DirectVertexBuilder<T>>(
attributes: GLAttributeList, attributes: GLAttributeList,
type: VertexType, type: GeometryType,
val maxElements: Int, val maxElements: Int,
) : AbstractVertexBuilder<DirectVertexBuilder<T>>(attributes, type) { ) : AbstractVertexBuilder<DirectVertexBuilder<T>>(attributes, type) {
val maxIndexCount = maxElements * type.indicies.size val maxIndexCount = maxElements * type.indicies.size
val maxVertexCount = maxElements * type.elements val maxVertexCount = maxElements * type.elements
override val vertexMemory = ByteBufferOutputStream.directLE(maxVertexCount) final override val elementIndexType: Int = when (maxIndexCount) {
override val elementMemory = ByteBufferOutputStream.directLE(maxIndexCount)
override val elementIndexType: Int = when (maxIndexCount) {
// api performance issue 102: glDrawElements uses element index type 'GL_UNSIGNED_BYTE' that is not optimal for the current hardware configuration; consider using 'GL_UNSIGNED_SHORT' instead // api performance issue 102: glDrawElements uses element index type 'GL_UNSIGNED_BYTE' that is not optimal for the current hardware configuration; consider using 'GL_UNSIGNED_SHORT' instead
// in 0 .. 255 -> GL_UNSIGNED_BYTE // in 0 .. 255 -> GL_UNSIGNED_BYTE
in 0 .. 65535 -> GL46.GL_UNSIGNED_SHORT in 0 .. 65535 -> GL46.GL_UNSIGNED_SHORT
else -> GL46.GL_UNSIGNED_INT else -> GL46.GL_UNSIGNED_INT
} }
final override val vertexMemory = ByteBufferOutputStream.directLE(maxVertexCount * attributes.stride)
final override val elementMemory = ByteBufferOutputStream.directLE(maxIndexCount * (if (elementIndexType == GL46.GL_UNSIGNED_SHORT) 2 else 4))
override fun ensureIndexCapacity() { override fun ensureIndexCapacity() {
if (vertexCount >= maxVertexCount) { if (vertexCount > maxVertexCount) {
throw IndexOutOfBoundsException("Vertex count overflow (can hold max of $maxElements elements, that's $maxVertexCount vertexes)") throw IndexOutOfBoundsException("Vertex count overflow (can hold max of $maxElements elements, that's $maxVertexCount vertexes)")
} }
} }

View File

@ -1,9 +1,9 @@
package ru.dbotthepony.kstarbound.client.gl.vertex package ru.dbotthepony.kstarbound.client.gl.vertex
enum class VertexType(val elements: Int, val indicies: IntArray) { enum class GeometryType(val elements: Int, val indicies: IntArray) {
LINES(2, intArrayOf(0, 1)), LINES(2, intArrayOf(0, 1)),
TRIANGLES(3, intArrayOf(0, 1, 2)), TRIANGLES(3, intArrayOf(0, 1, 2)),
QUADS(4, intArrayOf(0, 1, 2, 1, 2, 3)), QUADS(4, intArrayOf(0, 1, 2, 1, 2, 3)),
QUADS_AS_LINES(4, intArrayOf(0, 1, 0, 2, 1, 3, 2, 3)), QUADS_AS_LINES(4, intArrayOf(0, 1, 0, 2, 1, 3, 2, 3)),
QUADS_AS_LINES_WIREFRAME(4, intArrayOf(0, 1, 0, 2, 1, 3, 2, 3, 0, 3, 1, 2)), QUADS_AS_LINES_WIREFRAME(4, intArrayOf(0, 1, 0, 2, 1, 3, 2, 3, 0, 3, 1, 2)),
} }

View File

@ -17,7 +17,7 @@ import java.nio.ByteBuffer
*/ */
class HeapVertexBuilder( class HeapVertexBuilder(
attributes: GLAttributeList, attributes: GLAttributeList,
type: VertexType, type: GeometryType,
) : AbstractVertexBuilder<HeapVertexBuilder>(attributes, type) { ) : AbstractVertexBuilder<HeapVertexBuilder>(attributes, type) {
override val vertexMemory = FastByteArrayOutputStream() override val vertexMemory = FastByteArrayOutputStream()
override val elementMemory = FastByteArrayOutputStream() override val elementMemory = FastByteArrayOutputStream()

View File

@ -11,7 +11,7 @@ import java.io.Closeable
class StreamVertexBuilder( class StreamVertexBuilder(
val state: GLStateTracker, val state: GLStateTracker,
attributes: GLAttributeList, attributes: GLAttributeList,
type: VertexType, type: GeometryType,
maxElements: Int, maxElements: Int,
) : DirectVertexBuilder<StreamVertexBuilder>(attributes, type, maxElements), Closeable { ) : DirectVertexBuilder<StreamVertexBuilder>(attributes, type, maxElements), Closeable {
private val vao = state.newVAO() private val vao = state.newVAO()

View File

@ -5,6 +5,7 @@ import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.client.ClientChunk import ru.dbotthepony.kstarbound.client.ClientChunk
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kstarbound.client.gl.vertex.quadRotatedZ
import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile
import ru.dbotthepony.kvector.matrix.Matrix4fStack import ru.dbotthepony.kvector.matrix.Matrix4fStack

View File

@ -9,7 +9,8 @@ import ru.dbotthepony.kstarbound.client.freetype.struct.FT_Pixel_Mode
import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList
import ru.dbotthepony.kstarbound.client.gl.vertex.HeapVertexBuilder import ru.dbotthepony.kstarbound.client.gl.vertex.HeapVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexType import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
import ru.dbotthepony.kstarbound.client.gl.vertex.quad
import ru.dbotthepony.kvector.matrix.Matrix4fStack import ru.dbotthepony.kvector.matrix.Matrix4fStack
import ru.dbotthepony.kvector.vector.Color import ru.dbotthepony.kvector.vector.Color
@ -307,7 +308,7 @@ class Font(
ebo.bind() ebo.bind()
vbo.bind() vbo.bind()
val builder = HeapVertexBuilder(GLAttributeList.VERTEX_2D_TEXTURE, VertexType.QUADS) val builder = HeapVertexBuilder(GLAttributeList.VERTEX_2D_TEXTURE, GeometryType.QUADS)
builder.quad(0f, 0f, width, height, QuadTransformers.uv()) builder.quad(0f, 0f, width, height, QuadTransformers.uv())
builder.upload(vbo, ebo, GL_STATIC_DRAW) builder.upload(vbo, ebo, GL_STATIC_DRAW)

View File

@ -168,7 +168,7 @@ private enum class TileRenderTesselateResult {
HALT HALT
} }
private fun vertexTextureBuilder() = HeapVertexBuilder(GLAttributeList.TILE, VertexType.QUADS) private fun vertexTextureBuilder() = HeapVertexBuilder(GLAttributeList.TILE, GeometryType.QUADS)
private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester { private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester {
override fun test(thisTile: ITileState, otherTile: ITileState): Boolean { override fun test(thisTile: ITileState, otherTile: ITileState): Boolean {

View File

@ -8,6 +8,5 @@ uniform sampler2D _texture;
uniform vec4 _color; uniform vec4 _color;
void main() { void main() {
float sampled = texture(_texture, _uv_out).r; _color_out = _color * texture(_texture, _uv_out).r;
_color_out = vec4(_color.x * sampled, _color.y * sampled, _color.z * sampled, _color.w * sampled);
} }

View File

@ -0,0 +1,11 @@
#version 460
in vec2 oUVCoords;
out vec4 resultColor;
uniform vec4 baselineColor;
void main() {
resultColor = vec4(baselineColor) * smoothstep(0.0, 1.0, 1.0 - sqrt(pow(oUVCoords.x - 0.5, 2.0) + pow(oUVCoords.y - 0.5, 2.0)) * 2.0);
}

View File

@ -0,0 +1,14 @@
#version 460
layout (location = 0) in vec2 vertexPos;
layout (location = 1) in vec2 uvCoords;
out vec2 oUVCoords;
uniform mat4 transform;
void main() {
gl_Position = transform * vec4(vertexPos, 0.0, 1.0);
oUVCoords = uvCoords;
}

View File

@ -0,0 +1,10 @@
#version 460
uniform vec2 lightPosition;
out vec4 resultColor;
void main() {
resultColor = vec4(0.0, 0.0, 0.0, 0.0);
}

View File

@ -0,0 +1,36 @@
#version 460
layout (lines) in;
layout (triangle_strip, max_vertices = 5) out;
uniform mat4 transform;
uniform vec2 lightPosition;
in vec2 originalPos[];
void main() {
vec4 a = gl_in[0].gl_Position;
vec4 b = gl_in[1].gl_Position;
vec2 aInv = originalPos[0];
vec2 bInv = originalPos[1];
gl_Position = b;
EmitVertex();
gl_Position = a;
EmitVertex();
gl_Position = transform * vec4(aInv + (aInv - lightPosition) * 10000, 0, 1);
EmitVertex();
gl_Position = transform * vec4(bInv + (bInv - lightPosition) * 10000, 0, 1);
EmitVertex();
gl_Position = b;
EmitVertex();
EndPrimitive();
}

View File

@ -0,0 +1,13 @@
#version 460
layout (location = 0) in vec2 vertexPos;
uniform mat4 transform;
out vec2 originalPos;
void main() {
gl_Position = transform * vec4(vertexPos, 0.0, 1.0);
originalPos = vertexPos;
}

View File

@ -0,0 +1,10 @@
#version 460
out vec4 resultColor;
uniform vec4 color;
void main() {
resultColor = color;
}

View File

@ -0,0 +1,8 @@
#version 460
layout (location = 0) in vec2 vertexPos;
void main() {
gl_Position = vec4(vertexPos, 0.0, 1.0);
}

View File

@ -0,0 +1,11 @@
#version 460
out vec4 resultColor;
in vec2 uvPos;
uniform sampler2D texture0;
void main() {
resultColor = texture(texture0, uvPos);
}

View File

@ -0,0 +1,12 @@
#version 460
layout (location = 0) in vec2 vertexPos;
layout (location = 1) in vec2 inUVPos;
out vec2 uvPos;
void main() {
gl_Position = vec4(vertexPos, 0.0, 1.0);
uvPos = inUVPos;
}