Remove unused graphics stuff, merge GLStateTracker with StarboundClient

This commit is contained in:
DBotThePony 2023-09-17 21:25:20 +07:00
parent ef52700ff2
commit 3ad0e78c10
Signed by: DBot
GPG Key ID: DCC23B5715498507
41 changed files with 957 additions and 1347 deletions

View File

@ -82,7 +82,7 @@ dependencies {
implementation("com.github.jnr:jnr-ffi:2.2.13") implementation("com.github.jnr:jnr-ffi:2.2.13")
implementation("ru.dbotthepony:kbox2d:2.4.1.6") implementation("ru.dbotthepony:kbox2d:2.4.1.6")
implementation("ru.dbotthepony:kvector:2.5.0") implementation("ru.dbotthepony:kvector:2.6.0")
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5") implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
} }

View File

@ -157,10 +157,10 @@ fun main() {
//client.camera.pos = Vector2f(0f, 0f) //client.camera.pos = Vector2f(0f, 0f)
client.onDrawGUI { client.onDrawGUI {
client.gl.font.render("${ent.position}", y = 100f, scale = 0.25f) client.font.render("${ent.position}", y = 100f, scale = 0.25f)
client.gl.font.render("${ent.movement.velocity}", y = 120f, scale = 0.25f) client.font.render("${ent.movement.velocity}", y = 120f, scale = 0.25f)
client.gl.font.render("Camera: ${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f) client.font.render("Camera: ${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f)
client.gl.font.render("World chunk: ${client.world!!.chunkFromCell(client.camera.pos.toDoubleVector())}", y = 160f, scale = 0.25f) client.font.render("World chunk: ${client.world!!.chunkFromCell(client.camera.pos.toDoubleVector())}", y = 160f, scale = 0.25f)
} }
client.onPreDrawWorld { client.onPreDrawWorld {
@ -219,10 +219,10 @@ fun main() {
lightRenderer.renderOutputAdditive() lightRenderer.renderOutputAdditive()
}*/ }*/
client.gl.box2dRenderer.drawShapes = false client.box2dRenderer.drawShapes = false
client.gl.box2dRenderer.drawPairs = false client.box2dRenderer.drawPairs = false
client.gl.box2dRenderer.drawAABB = false client.box2dRenderer.drawAABB = false
client.gl.box2dRenderer.drawJoints = false client.box2dRenderer.drawJoints = false
//ent.spawn() //ent.spawn()

View File

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

View File

@ -1,34 +1,62 @@
package ru.dbotthepony.kstarbound.client package ru.dbotthepony.kstarbound.client
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.lwjgl.BufferUtils import org.lwjgl.BufferUtils
import org.lwjgl.glfw.Callbacks import org.lwjgl.glfw.Callbacks
import org.lwjgl.glfw.GLFW import org.lwjgl.glfw.GLFW
import org.lwjgl.glfw.GLFWErrorCallback import org.lwjgl.glfw.GLFWErrorCallback
import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL46.* import org.lwjgl.opengl.GL46.*
import org.lwjgl.opengl.GLCapabilities
import org.lwjgl.system.MemoryStack import org.lwjgl.system.MemoryStack
import org.lwjgl.system.MemoryUtil 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.freetype.FreeType
import ru.dbotthepony.kstarbound.client.freetype.InvalidArgumentException
import ru.dbotthepony.kstarbound.client.gl.BlendFunc import ru.dbotthepony.kstarbound.client.gl.BlendFunc
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.gl.GLFrameBuffer
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
import ru.dbotthepony.kstarbound.client.gl.ScissorRect
import ru.dbotthepony.kstarbound.client.gl.VBOType
import ru.dbotthepony.kstarbound.client.gl.VertexArrayObject
import ru.dbotthepony.kstarbound.client.gl.VertexBufferObject
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import ru.dbotthepony.kstarbound.client.gl.properties.GLStateFuncTracker
import ru.dbotthepony.kstarbound.client.gl.properties.GLStateGenericTracker
import ru.dbotthepony.kstarbound.client.gl.properties.GLStateIntTracker
import ru.dbotthepony.kstarbound.client.gl.properties.GLStateSwitchTracker
import ru.dbotthepony.kstarbound.client.gl.properties.TexturesTracker
import ru.dbotthepony.kstarbound.client.gl.shader.GLPrograms
import ru.dbotthepony.kstarbound.client.gl.shader.GLShader
import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram
import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList
import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
import ru.dbotthepony.kstarbound.client.input.UserInput import ru.dbotthepony.kstarbound.client.input.UserInput
import ru.dbotthepony.kstarbound.client.render.Box2DRenderer
import ru.dbotthepony.kstarbound.client.render.Camera import ru.dbotthepony.kstarbound.client.render.Camera
import ru.dbotthepony.kstarbound.client.render.Font
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
import ru.dbotthepony.kstarbound.client.render.TextAlignY import ru.dbotthepony.kstarbound.client.render.TextAlignY
import ru.dbotthepony.kstarbound.client.render.TileRenderers import ru.dbotthepony.kstarbound.client.render.TileRenderers
import ru.dbotthepony.kstarbound.client.world.ClientWorld import ru.dbotthepony.kstarbound.client.world.ClientWorld
import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
import ru.dbotthepony.kstarbound.util.JVMTimeSource import ru.dbotthepony.kstarbound.util.JVMTimeSource
import ru.dbotthepony.kstarbound.util.PausableTimeSource
import ru.dbotthepony.kstarbound.util.formatBytesShort import ru.dbotthepony.kstarbound.util.formatBytesShort
import ru.dbotthepony.kstarbound.world.LightCalculator import ru.dbotthepony.kstarbound.world.LightCalculator
import ru.dbotthepony.kstarbound.world.api.ICellAccess import ru.dbotthepony.kstarbound.world.api.ICellAccess
import ru.dbotthepony.kstarbound.world.api.IChunkCell import ru.dbotthepony.kstarbound.world.api.IChunkCell
import ru.dbotthepony.kvector.api.IStruct4f
import ru.dbotthepony.kvector.arrays.Matrix4f import ru.dbotthepony.kvector.arrays.Matrix4f
import ru.dbotthepony.kvector.arrays.Matrix4fStack
import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2d
@ -36,17 +64,45 @@ import ru.dbotthepony.kvector.vector.Vector2f
import ru.dbotthepony.kvector.vector.Vector2i import ru.dbotthepony.kvector.vector.Vector2i
import ru.dbotthepony.kvector.vector.Vector3f import ru.dbotthepony.kvector.vector.Vector3f
import java.io.Closeable import java.io.Closeable
import java.io.File
import java.lang.ref.Cleaner
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import java.time.Duration
import java.util.*
import java.util.concurrent.locks.LockSupport import java.util.concurrent.locks.LockSupport
import java.util.concurrent.locks.ReentrantLock
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.math.roundToInt
class StarboundClient : Closeable { class StarboundClient : Closeable {
val time = PausableTimeSource(JVMTimeSource.INSTANCE)
val window: Long val window: Long
val camera = Camera(this) val camera = Camera(this)
val input = UserInput() val input = UserInput()
val gl: GLStateTracker val thread: Thread = Thread.currentThread()
val capabilities: GLCapabilities
var viewportX: Int = 0
private set
var viewportY: Int = 0
private set
var viewportWidth: Int = 0
private set
var viewportHeight: Int = 0
private set
var viewportCellX = 0
private set
var viewportCellY = 0
private set
var viewportCellWidth = 0
private set
var viewportCellHeight = 0
private set
var viewportRectangle = AABB.rectangle(Vector2d.ZERO, 0.0, 0.0)
private set
var fullbright = true
var gameTerminated = false var gameTerminated = false
private set private set
@ -65,9 +121,519 @@ class StarboundClient : Closeable {
private set private set
get() = Matrix4f.unmodifiable(field) get() = Matrix4f.unmodifiable(field)
var isRenderingGame = true
private set
private val scissorStack = LinkedList<ScissorRect>()
private val cleanerBacklog = ArrayList<() -> Unit>()
private val onDrawGUI = ArrayList<() -> Unit>()
private val onPreDrawWorld = ArrayList<(LayeredRenderer) -> Unit>()
private val onPostDrawWorld = ArrayList<() -> Unit>()
private val onPostDrawWorldOnce = ArrayList<(LayeredRenderer) -> Unit>()
private val onViewportChanged = ArrayList<(width: Int, height: Int) -> Unit>()
private val terminateCallbacks = ArrayList<() -> Unit>()
private val startupTextList = ArrayList<String>() private val startupTextList = ArrayList<String>()
private var finishStartupRendering = System.currentTimeMillis() + 4000L private var finishStartupRendering = System.currentTimeMillis() + 4000L
private val cleaner = Cleaner.create { r ->
val thread = Thread(r, "OpenGL Cleaner for '${thread.name}'")
thread.priority = 2
thread
}
@Volatile
var objectsCleaned = 0L
private set
@Volatile
var gcHits = 0L
private set
init {
check(CLIENTS.get() == null) { "Already has OpenGL context existing at ${Thread.currentThread()}!" }
CLIENTS.set(this)
lock.lock()
try {
if (!glfwInitialized) {
check(GLFW.glfwInit()) { "Unable to initialize GLFW" }
glfwInitialized = true
GLFWErrorCallback.create { error, description ->
LOGGER.error("LWJGL error {}: {}", error, description)
}.set()
}
} finally {
lock.unlock()
}
GLFW.glfwDefaultWindowHints()
GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE)
GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_TRUE)
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 4)
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 6)
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE)
window = GLFW.glfwCreateWindow(800, 600, "KStarbound", MemoryUtil.NULL, MemoryUtil.NULL)
require(window != MemoryUtil.NULL) { "Unable to create GLFW window" }
startupTextList.add("Created GLFW window")
input.installCallback(window)
GLFW.glfwMakeContextCurrent(window)
// This line is critical for LWJGL's interoperation with GLFW's
// OpenGL context, or any context that is managed externally.
// LWJGL detects the context that is current in the current thread,
// creates the GLCapabilities instance and makes the OpenGL
// bindings available for use.
capabilities = GL.createCapabilities()
GLFW.glfwSetFramebufferSizeCallback(window) { _, w, h ->
if (w == 0 || h == 0) {
isRenderingGame = false
} else {
isRenderingGame = true
setViewport(0, 0, w, h)
viewportMatrixScreen = updateViewportMatrixScreen()
viewportMatrixWorld = updateViewportMatrixWorld()
for (callback in onViewportChanged) {
callback.invoke(w, h)
}
}
}
val stack = MemoryStack.stackPush()
try {
val pWidth = stack.mallocInt(1)
val pHeight = stack.mallocInt(1)
GLFW.glfwGetWindowSize(window, pWidth, pHeight)
val vidmode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor())!!
GLFW.glfwSetWindowPos(
window,
(vidmode.width() - pWidth[0]) / 2,
(vidmode.height() - pHeight[0]) / 2
)
setViewport(0, 0, pWidth[0], pHeight[0])
viewportMatrixScreen = updateViewportMatrixScreen()
viewportMatrixWorld = updateViewportMatrixWorld()
} finally {
stack.close()
}
// vsync
GLFW.glfwSwapInterval(0)
GLFW.glfwShowWindow(window)
putDebugLog("Initialized GLFW window")
}
val programs = GLPrograms()
val flat2DLines by lazy { StreamVertexBuilder(GLAttributeList.VEC2F, GeometryType.LINES) }
val flat2DTriangles by lazy { StreamVertexBuilder(GLAttributeList.VEC2F, GeometryType.TRIANGLES) }
val flat2DTexturedQuads by lazy { StreamVertexBuilder(GLAttributeList.VERTEX_TEXTURE, GeometryType.QUADS) }
val quadWireframe by lazy { StreamVertexBuilder(GLAttributeList.VEC2F, GeometryType.QUADS_AS_LINES_WIREFRAME) }
// минимальное время хранения 5 минут и...
private val named2DTextures0: Cache<String, GLTexture2D> = Caffeine.newBuilder()
.expireAfterAccess(Duration.ofMinutes(5))
.build()
// ...бесконечное хранение пока кто-то все ещё использует текстуру
private val named2DTextures1: Cache<String, GLTexture2D> = Caffeine.newBuilder()
.weakValues()
.build()
private val missingTexture: GLTexture2D by lazy {
newTexture(missingTexturePath).upload(Starbound.readDirect(missingTexturePath), GL_RGBA, GL_RGBA).generateMips().also {
it.textureMinFilter = GL_NEAREST
it.textureMagFilter = GL_NEAREST
}
}
private val missingTexturePath = "/assetmissing.png"
val matrixStack = Matrix4fStack()
val freeType = FreeType()
val font = Font()
val box2dRenderer = Box2DRenderer()
fun registerCleanable(ref: Any, fn: (Int) -> Unit, nativeRef: Int): Cleaner.Cleanable {
val cleanable = cleaner.register(ref) {
objectsCleaned++
if (isSameThread()) {
fn(nativeRef)
checkForGLError()
} else {
gcHits++
synchronized(cleanerBacklog) {
cleanerBacklog.add {
fn(nativeRef)
checkForGLError()
}
}
}
}
return cleanable
}
fun cleanup() {
synchronized(cleanerBacklog) {
for (lambda in cleanerBacklog) {
lambda.invoke()
}
cleanerBacklog.clear()
}
}
var blend by GLStateSwitchTracker(GL_BLEND)
var scissor by GLStateSwitchTracker(GL_SCISSOR_TEST)
var cull by GLStateSwitchTracker(GL_CULL_FACE)
var cullMode by GLStateFuncTracker(::glCullFace, GL_BACK)
var textureUnpackAlignment by GLStateIntTracker(::glPixelStorei, GL_UNPACK_ALIGNMENT, 4)
var scissorRect by GLStateGenericTracker(ScissorRect(0, 0, 0, 0)) {
// require(it.x >= 0) { "Invalid X ${it.x}"}
// require(it.y >= 0) { "Invalid Y ${it.y}"}
require(it.width >= 0) { "Invalid width ${it.width}"}
require(it.height >= 0) { "Invalid height ${it.height}"}
glScissor(it.x, it.y, it.width, it.height)
}
var depthTest by GLStateSwitchTracker(GL_DEPTH_TEST)
var VBO: VertexBufferObject? = null
set(value) {
ensureSameThread()
if (field !== value) {
isMe(value?.client)
require(value?.isArray != false) { "Provided buffer object is not of Array type" }
glBindBuffer(GL_ARRAY_BUFFER, value?.pointer ?: 0)
checkForGLError("Setting Vertex Buffer Object")
field = value
}
}
var EBO: VertexBufferObject? = null
set(value) {
ensureSameThread()
if (field !== value) {
isMe(value?.client)
require(value?.isElementArray != false) { "Provided buffer object is not of Array type" }
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, value?.pointer ?: 0)
checkForGLError("Setting Element Buffer Object")
field = value
}
}
var VAO: VertexArrayObject? = null
set(value) {
ensureSameThread()
if (field !== value) {
isMe(value?.client)
glBindVertexArray(value?.pointer ?: 0)
checkForGLError("Setting Vertex Array Object")
field = value
}
}
var readFramebuffer: GLFrameBuffer? = null
set(value) {
ensureSameThread()
if (field === value) return
isMe(value?.client)
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?.client)
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
set(value) {
ensureSameThread()
if (value !== field) {
isMe(value?.client)
glUseProgram(value?.pointer ?: 0)
checkForGLError("Setting shader program")
field = value
}
}
var activeTexture = 0
set(value) {
ensureSameThread()
if (field != value) {
require(value >= 0) { "Invalid texture block $value" }
require(value < 80) { "Too big texture block index $value, OpenGL 4.6 guarantee only 80!" }
glActiveTexture(GL_TEXTURE0 + value)
checkForGLError()
field = value
}
}
var texture2D: GLTexture2D? by TexturesTracker(80)
var clearColor by GLStateGenericTracker<IStruct4f>(RGBAColor.WHITE) {
val (r, g, b, a) = it
glClearColor(r, g, b, a)
}
var blendFunc by GLStateGenericTracker(BlendFunc()) {
glBlendFuncSeparate(it.sourceColor.enum, it.destinationColor.enum, it.sourceAlpha.enum, it.destinationAlpha.enum)
}
init {
glActiveTexture(GL_TEXTURE0)
checkForGLError()
}
fun setViewport(x: Int, y: Int, width: Int, height: Int) {
ensureSameThread()
if (viewportX != x || viewportY != y || viewportWidth != width || viewportHeight != height) {
glViewport(x, y, width, height)
checkForGLError("Setting viewport")
viewportX = x
viewportY = y
viewportWidth = width
viewportHeight = height
}
}
fun pushScissorRect(x: Float, y: Float, width: Float, height: Float) {
return pushScissorRect(x.roundToInt(), y.roundToInt(), width.roundToInt(), height.roundToInt())
}
@Suppress("NAME_SHADOWING")
fun pushScissorRect(x: Int, y: Int, width: Int, height: Int) {
var x = x
var y = y
var width = width
var height = height
val peek = scissorStack.lastOrNull()
if (peek != null) {
x = x.coerceAtLeast(peek.x)
y = y.coerceAtLeast(peek.y)
width = width.coerceAtMost(peek.width)
height = height.coerceAtMost(peek.height)
if (peek.x == x && peek.y == y && peek.width == width && peek.height == height) {
scissorStack.add(peek)
return
}
}
val rect = ScissorRect(x, y, width, height)
scissorStack.add(rect)
scissorRect = rect
scissor = true
}
fun popScissorRect() {
scissorStack.removeLast()
val peek = scissorStack.lastOrNull()
if (peek == null) {
scissor = false
return
}
val y = viewportHeight - peek.y - peek.height
scissorRect = ScissorRect(peek.x, y, peek.width, peek.height)
}
val currentScissorRect get() = scissorStack.lastOrNull()
fun ensureSameThread() {
if (thread !== Thread.currentThread()) {
throw IllegalAccessException("Trying to access $this outside of $thread!")
}
}
fun isSameThread() = thread === Thread.currentThread()
fun newTexture(name: String = "<unknown>") = GLTexture2D(name)
fun loadTexture(path: String): GLTexture2D {
ensureSameThread()
return named2DTextures0.get(path) {
named2DTextures1.get(it) {
val data = Image.get(it)
if (data == null) {
LOGGER.error("Texture {} is missing! Falling back to {}", it, missingTexturePath)
missingTexture
} else {
newTexture(it).upload(data).also {
it.textureMinFilter = GL_NEAREST
it.textureMagFilter = GL_NEAREST
}
}
}
}
}
fun bind(obj: VertexBufferObject): VertexBufferObject {
if (obj.type == VBOType.ARRAY)
VBO = obj
else
EBO = obj
return obj
}
fun unbind(obj: VertexBufferObject): VertexBufferObject {
if (obj.type == VBOType.ARRAY)
if (obj == VBO)
VBO = null
else
if (obj == EBO)
EBO = null
return obj
}
fun bind(obj: VertexArrayObject): VertexArrayObject {
VAO = obj
return obj
}
fun unbind(obj: VertexArrayObject): VertexArrayObject {
if (obj == VAO)
VAO = null
return obj
}
fun newVBO() = VertexBufferObject.vbo()
fun newEBO() = VertexBufferObject.ebo()
fun newVAO() = VertexArrayObject()
inline fun quadWireframe(color: RGBAColor = RGBAColor.WHITE, lambda: (VertexBuilder) -> Unit) {
val builder = quadWireframe
builder.builder.begin()
lambda.invoke(builder.builder)
builder.upload()
programs.flat.use()
programs.flat.color = color
programs.flat.transform = matrixStack.last()
builder.draw(GL_LINES)
}
inline fun quadColor(lambda: (VertexBuilder) -> Unit) {
val builder = programs.flatColor.builder
builder.builder.begin()
lambda.invoke(builder.builder)
builder.upload()
programs.flatColor.use()
programs.flatColor.transform = matrixStack.last()
builder.draw(GL_TRIANGLES)
}
inline fun quadTexture(texture: GLTexture2D, lambda: (VertexBuilder) -> Unit) {
val builder = programs.textured2d.builder
builder.builder.begin()
lambda.invoke(builder.builder)
builder.upload()
activeTexture = 0
texture.bind()
programs.textured2d.use()
programs.textured2d.transform = matrixStack.last()
programs.textured2d.texture = 0
builder.draw(GL_TRIANGLES)
}
inline fun quadWireframe(value: AABB, color: RGBAColor = RGBAColor.WHITE, chain: (VertexBuilder) -> Unit = {}) {
quadWireframe(color) {
it.quad(value.mins.x.toFloat(), value.mins.y.toFloat(), value.maxs.x.toFloat(), value.maxs.y.toFloat())
chain(it)
}
}
fun vertex(file: File) = GLShader(file, GL_VERTEX_SHADER)
fun fragment(file: File) = GLShader(file, GL_FRAGMENT_SHADER)
fun vertex(contents: String) = GLShader(contents, GL_VERTEX_SHADER)
fun fragment(contents: String) = GLShader(contents, GL_FRAGMENT_SHADER)
fun internalVertex(file: String) = GLShader(readInternal(file), GL_VERTEX_SHADER)
fun internalFragment(file: String) = GLShader(readInternal(file), GL_FRAGMENT_SHADER)
fun internalGeometry(file: String) = GLShader(readInternal(file), GL_GEOMETRY_SHADER)
fun putDebugLog(text: String, replace: Boolean = false) { fun putDebugLog(text: String, replace: Boolean = false) {
if (replace) { if (replace) {
if (startupTextList.isEmpty()) { if (startupTextList.isEmpty()) {
@ -82,6 +648,12 @@ class StarboundClient : Closeable {
finishStartupRendering = System.currentTimeMillis() + 4000L finishStartupRendering = System.currentTimeMillis() + 4000L
} }
private fun isMe(state: StarboundClient?) {
if (state != null && state != this) {
throw InvalidArgumentException("Provided object does not belong to $this state tracker (belongs to $state)")
}
}
private fun updateViewportMatrixScreen(): Matrix4f { private fun updateViewportMatrixScreen(): Matrix4f {
return Matrix4f.ortho(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 0.1f, 100f).translate(Vector3f(z = 2f)) return Matrix4f.ortho(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 0.1f, 100f).translate(Vector3f(z = 2f))
} }
@ -157,96 +729,21 @@ class StarboundClient : Closeable {
return Vector2f(relativeX, relativeY) return Vector2f(relativeX, relativeY)
} }
var isRenderingGame = true
private set
init {
GLFWErrorCallback.create { error, description ->
LOGGER.error("LWJGL error {}: {}", error, description)
}.set()
check(GLFW.glfwInit()) { "Unable to initialize GLFW" }
GLFW.glfwDefaultWindowHints()
GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE)
GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_TRUE)
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 4)
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 6)
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE)
window = GLFW.glfwCreateWindow(800, 600, "KStarbound", MemoryUtil.NULL, MemoryUtil.NULL)
require(window != MemoryUtil.NULL) { "Unable to create GLFW window" }
startupTextList.add("Created GLFW window")
input.installCallback(window)
GLFW.glfwMakeContextCurrent(window)
gl = GLStateTracker(this)
GLFW.glfwSetFramebufferSizeCallback(window) { _, w, h ->
if (w == 0 || h == 0) {
isRenderingGame = false
} else {
isRenderingGame = true
gl.setViewport(0, 0, w, h)
viewportMatrixScreen = updateViewportMatrixScreen()
viewportMatrixWorld = updateViewportMatrixWorld()
for (callback in onViewportChanged) {
callback.invoke(w, h)
}
}
}
val stack = MemoryStack.stackPush()
try {
val pWidth = stack.mallocInt(1)
val pHeight = stack.mallocInt(1)
GLFW.glfwGetWindowSize(window, pWidth, pHeight)
val vidmode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor())!!
GLFW.glfwSetWindowPos(
window,
(vidmode.width() - pWidth[0]) / 2,
(vidmode.height() - pHeight[0]) / 2
)
gl.setViewport(0, 0, pWidth[0], pHeight[0])
viewportMatrixScreen = updateViewportMatrixScreen()
viewportMatrixWorld = updateViewportMatrixWorld()
} finally {
stack.close()
}
// vsync
GLFW.glfwSwapInterval(0)
GLFW.glfwShowWindow(window)
putDebugLog("Initialized GLFW window")
}
val viewportWidth by gl::viewportWidth
val viewportHeight by gl::viewportHeight
val tileRenderers = TileRenderers(this) val tileRenderers = TileRenderers(this)
var world: ClientWorld? = ClientWorld(this, 0L, Vector2i(3000, 2000), true) var world: ClientWorld? = ClientWorld(this, 0L, Vector2i(3000, 2000), true)
init { init {
putDebugLog("Initialized OpenGL context") putDebugLog("Initialized OpenGL context")
gl.clearColor = RGBAColor.SLATE_GRAY clearColor = RGBAColor.SLATE_GRAY
gl.blend = true blend = true
gl.blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
} }
var frameRenderTime = 0.0 var frameRenderTime = 0.0
private set private set
private var lastRender = JVMTimeSource.INSTANCE.seconds
val framesPerSecond get() = if (frameRenderTime == 0.0) 1.0 else 1.0 / frameRenderTime val framesPerSecond get() = if (frameRenderTime == 0.0) 1.0 else 1.0 / frameRenderTime
private val frameRenderTimes = DoubleArray(60) { 1.0 } private val frameRenderTimes = DoubleArray(60) { 1.0 }
@ -267,17 +764,6 @@ class StarboundClient : Closeable {
val settings = ClientSettings() val settings = ClientSettings()
var viewportCellX = 0
private set
var viewportCellY = 0
private set
var viewportCellWidth = 0
private set
var viewportCellHeight = 0
private set
var viewportRectangle = AABB.rectangle(Vector2d.ZERO, 0.0, 0.0)
private set
val viewportCells: ICellAccess = object : ICellAccess { val viewportCells: ICellAccess = object : ICellAccess {
override fun getCell(x: Int, y: Int): IChunkCell? { override fun getCell(x: Int, y: Int): IChunkCell? {
return world?.getCell(x + viewportCellX, y + viewportCellY) return world?.getCell(x + viewportCellX, y + viewportCellY)
@ -291,11 +777,9 @@ class StarboundClient : Closeable {
var viewportLighting = LightCalculator(viewportCells, viewportCellWidth, viewportCellHeight) var viewportLighting = LightCalculator(viewportCells, viewportCellWidth, viewportCellHeight)
private set private set
val viewportLightingTexture = gl.newTexture("Viewport Lighting") val viewportLightingTexture = newTexture("Viewport Lighting")
private var viewportLightingMem: ByteBuffer? = null private var viewportLightingMem: ByteBuffer? = null
var fullbright = true
fun updateViewportParams() { fun updateViewportParams() {
viewportRectangle = AABB.rectangle( viewportRectangle = AABB.rectangle(
camera.pos.toDoubleVector(), camera.pos.toDoubleVector(),
@ -319,12 +803,6 @@ class StarboundClient : Closeable {
} }
} }
private val onDrawGUI = ArrayList<() -> Unit>()
private val onPreDrawWorld = ArrayList<(LayeredRenderer) -> Unit>()
private val onPostDrawWorld = ArrayList<() -> Unit>()
private val onPostDrawWorldOnce = ArrayList<(LayeredRenderer) -> Unit>()
private val onViewportChanged = ArrayList<(width: Int, height: Int) -> Unit>()
fun onViewportChanged(callback: (width: Int, height: Int) -> Unit) { fun onViewportChanged(callback: (width: Int, height: Int) -> Unit) {
onViewportChanged.add(callback) onViewportChanged.add(callback)
} }
@ -345,10 +823,8 @@ class StarboundClient : Closeable {
onPostDrawWorldOnce.add(lambda) onPostDrawWorldOnce.add(lambda)
} }
private var lastRender = JVMTimeSource.INSTANCE.seconds
fun renderFrame(): Boolean { fun renderFrame(): Boolean {
gl.ensureSameThread() ensureSameThread()
val diff = JVMTimeSource.INSTANCE.seconds - lastRender val diff = JVMTimeSource.INSTANCE.seconds - lastRender
@ -367,7 +843,7 @@ class StarboundClient : Closeable {
val world = world val world = world
if (!isRenderingGame) { if (!isRenderingGame) {
gl.cleanup() cleanup()
GLFW.glfwPollEvents() GLFW.glfwPollEvents()
if (world != null) { if (world != null) {
@ -385,11 +861,11 @@ class StarboundClient : Closeable {
if (Starbound.initialized) if (Starbound.initialized)
world.think() world.think()
gl.clearColor = RGBAColor.SLATE_GRAY clearColor = RGBAColor.SLATE_GRAY
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
gl.matrixStack.clear(viewportMatrixWorld) matrixStack.clear(viewportMatrixWorld)
gl.matrixStack.push().last() matrixStack.push().last()
.translateWithMultiplication(viewportWidth / 2f, viewportHeight / 2f, 2f) // центр экрана + координаты отрисовки мира .translateWithMultiplication(viewportWidth / 2f, viewportHeight / 2f, 2f) // центр экрана + координаты отрисовки мира
.scale(x = settings.zoom * PIXELS_IN_STARBOUND_UNITf, y = settings.zoom * 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) // перемещаем вид к камере
@ -409,7 +885,7 @@ class StarboundClient : Closeable {
layers = layers, layers = layers,
size = viewportRectangle) size = viewportRectangle)
layers.render(gl.matrixStack) layers.render(matrixStack)
val viewportLightingMem = viewportLightingMem val viewportLightingMem = viewportLightingMem
@ -420,8 +896,8 @@ class StarboundClient : Closeable {
viewportLighting.calculate(viewportLightingMem, viewportLighting.width.coerceAtMost(4096), viewportLighting.height.coerceAtMost(4096)) viewportLighting.calculate(viewportLightingMem, viewportLighting.width.coerceAtMost(4096), viewportLighting.height.coerceAtMost(4096))
viewportLightingMem.position(0) viewportLightingMem.position(0)
val old = gl.textureUnpackAlignment val old = textureUnpackAlignment
gl.textureUnpackAlignment = if (viewportLighting.width.coerceAtMost(4096) % 4 == 0) 4 else 1 textureUnpackAlignment = if (viewportLighting.width.coerceAtMost(4096) % 4 == 0) 4 else 1
viewportLightingTexture.upload( viewportLightingTexture.upload(
GL_RGB, GL_RGB,
@ -432,16 +908,16 @@ class StarboundClient : Closeable {
viewportLightingMem viewportLightingMem
) )
gl.textureUnpackAlignment = old textureUnpackAlignment = old
viewportLightingTexture.textureMinFilter = GL_LINEAR viewportLightingTexture.textureMinFilter = GL_LINEAR
//viewportLightingTexture.textureMagFilter = GL_NEAREST //viewportLightingTexture.textureMagFilter = GL_NEAREST
//viewportLightingTexture.generateMips() //viewportLightingTexture.generateMips()
gl.blendFunc = BlendFunc.MULTIPLY_BY_SRC blendFunc = BlendFunc.MULTIPLY_BY_SRC
gl.quadTexture(viewportLightingTexture) { quadTexture(viewportLightingTexture) {
it.quad( it.quad(
(viewportCellX).toFloat(), (viewportCellX).toFloat(),
(viewportCellY).toFloat(), (viewportCellY).toFloat(),
@ -451,7 +927,7 @@ class StarboundClient : Closeable {
) )
} }
gl.blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
} }
world.physics.debugDraw() world.physics.debugDraw()
@ -460,10 +936,10 @@ class StarboundClient : Closeable {
lambda.invoke() lambda.invoke()
} }
gl.matrixStack.pop() matrixStack.pop()
} }
gl.matrixStack.clear(viewportMatrixScreen) matrixStack.clear(viewportMatrixScreen)
val thisTime = System.currentTimeMillis() val thisTime = System.currentTimeMillis()
@ -474,20 +950,20 @@ class StarboundClient : Closeable {
alpha = (finishStartupRendering - thisTime) / 1000f alpha = (finishStartupRendering - thisTime) / 1000f
} }
gl.matrixStack.push() matrixStack.push()
gl.matrixStack.last().translateWithMultiplication(y = viewportHeight.toFloat()) matrixStack.last().translateWithMultiplication(y = viewportHeight.toFloat())
var shade = 255 var shade = 255
for (i in startupTextList.size - 1 downTo 0) { for (i in startupTextList.size - 1 downTo 0) {
val size = gl.font.render(startupTextList[i], alignY = TextAlignY.BOTTOM, scale = 0.4f, color = RGBAColor(shade / 255f, shade / 255f, shade / 255f, alpha)) val size = font.render(startupTextList[i], alignY = TextAlignY.BOTTOM, scale = 0.4f, color = RGBAColor(shade / 255f, shade / 255f, shade / 255f, alpha))
gl.matrixStack.last().translateWithMultiplication(y = -size.height * 1.2f) matrixStack.last().translateWithMultiplication(y = -size.height * 1.2f)
if (shade > 120) { if (shade > 120) {
shade -= 10 shade -= 10
} }
} }
gl.matrixStack.pop() matrixStack.pop()
} }
for (fn in onDrawGUI) { for (fn in onDrawGUI) {
@ -496,8 +972,8 @@ class StarboundClient : Closeable {
val runtime = Runtime.getRuntime() val runtime = Runtime.getRuntime()
gl.font.render("FPS: ${(averageFramesPerSecond * 100f).toInt() / 100f}", scale = 0.4f) font.render("FPS: ${(averageFramesPerSecond * 100f).toInt() / 100f}", scale = 0.4f)
gl.font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = gl.font.lineHeight * 0.5f, scale = 0.4f) font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = font.lineHeight * 0.5f, scale = 0.4f)
GLFW.glfwSwapBuffers(window) GLFW.glfwSwapBuffers(window)
GLFW.glfwPollEvents() GLFW.glfwPollEvents()
@ -505,13 +981,11 @@ class StarboundClient : Closeable {
camera.think(Starbound.TICK_TIME_ADVANCE) camera.think(Starbound.TICK_TIME_ADVANCE)
gl.cleanup() cleanup()
return true return true
} }
private val terminateCallbacks = ArrayList<() -> Unit>()
fun onTermination(lambda: () -> Unit) { fun onTermination(lambda: () -> Unit) {
terminateCallbacks.add(lambda) terminateCallbacks.add(lambda)
} }
@ -537,5 +1011,25 @@ class StarboundClient : Closeable {
companion object { companion object {
private val LOGGER = LogManager.getLogger(StarboundClient::class.java) private val LOGGER = LogManager.getLogger(StarboundClient::class.java)
private val CLIENTS = ThreadLocal<StarboundClient>()
@JvmStatic
fun current() = checkNotNull(CLIENTS.get()) { "No client registered to current thread (${Thread.currentThread()})" }
@JvmStatic
fun currentOrNull(): StarboundClient? = CLIENTS.get()
private val lock = ReentrantLock()
@Volatile
private var glfwInitialized = false
@JvmStatic
fun readInternal(file: String): String {
return ClassLoader.getSystemClassLoader().getResourceAsStream(file)!!.bufferedReader()
.let {
val read = it.readText()
it.close()
read
}
}
} }
} }

View File

@ -5,28 +5,23 @@ import org.lwjgl.opengl.GL30.GL_FRAMEBUFFER
import org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_COMPLETE import org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_COMPLETE
import org.lwjgl.opengl.GL30.GL_LINEAR import org.lwjgl.opengl.GL30.GL_LINEAR
import org.lwjgl.opengl.GL30.GL_RGB 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.GL_TEXTURE_2D
import org.lwjgl.opengl.GL30.glCheckFramebufferStatus
import org.lwjgl.opengl.GL30.glFramebufferTexture2D import org.lwjgl.opengl.GL30.glFramebufferTexture2D
import org.lwjgl.opengl.GL45.glCheckNamedFramebufferStatus import org.lwjgl.opengl.GL45.glCheckNamedFramebufferStatus
import org.lwjgl.opengl.GL45.glNamedFramebufferTexture
import org.lwjgl.opengl.GL46 import org.lwjgl.opengl.GL46
import ru.dbotthepony.kstarbound.client.StarboundClient
class GLFrameBuffer(val state: GLStateTracker) { class GLFrameBuffer {
init { val client = StarboundClient.current()
state.ensureSameThread()
}
val pointer = GL46.glGenFramebuffers() val pointer = GL46.glGenFramebuffers()
init { init {
checkForGLError("Creating framebuffer") checkForGLError("Creating framebuffer")
state.registerCleanable(this, GL46::glDeleteFramebuffers, pointer) client.registerCleanable(this, GL46::glDeleteFramebuffers, pointer)
} }
val isComplete: Boolean get() { val isComplete: Boolean get() {
state.ensureSameThread() client.ensureSameThread()
return glCheckNamedFramebufferStatus(pointer, GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE return glCheckNamedFramebufferStatus(pointer, GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE
} }
@ -38,16 +33,16 @@ class GLFrameBuffer(val state: GLStateTracker) {
throw IllegalStateException("Already has texture attached") throw IllegalStateException("Already has texture attached")
} }
val texture = GLTexture2D(state, "framebuffer_$pointer") val texture = GLTexture2D("framebuffer_$pointer")
texture.allocate(format, width, height) texture.allocate(format, width, height)
val old = state.framebuffer val old = client.framebuffer
bind() bind()
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.pointer, 0) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.pointer, 0)
checkForGLError() checkForGLError()
this.texture = texture this.texture = texture
texture.textureMinFilter = GL_LINEAR texture.textureMinFilter = GL_LINEAR
state.framebuffer = old client.framebuffer = old
} }
fun reattachTexture(width: Int, height: Int, format: Int = GL_RGB) { fun reattachTexture(width: Int, height: Int, format: Int = GL_RGB) {
@ -56,24 +51,24 @@ class GLFrameBuffer(val state: GLStateTracker) {
} }
fun bind() { fun bind() {
state.framebuffer = this client.framebuffer = this
} }
fun bindWrite() { fun bindWrite() {
state.writeFramebuffer = this client.writeFramebuffer = this
} }
fun bindRead() { fun bindRead() {
state.readFramebuffer = this client.readFramebuffer = this
} }
fun unbind() { fun unbind() {
if (state.writeFramebuffer == this) { if (client.writeFramebuffer == this) {
state.writeFramebuffer = null client.writeFramebuffer = null
} }
if (state.readFramebuffer == this) { if (client.readFramebuffer == this) {
state.readFramebuffer = null client.readFramebuffer = null
} }
} }
} }

View File

@ -1,640 +0,0 @@
package ru.dbotthepony.kstarbound.client.gl
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import org.apache.logging.log4j.LogManager
import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL46.*
import org.lwjgl.opengl.GLCapabilities
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.freetype.FreeType
import ru.dbotthepony.kstarbound.client.freetype.InvalidArgumentException
import ru.dbotthepony.kstarbound.client.gl.shader.GLPrograms
import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram
import ru.dbotthepony.kstarbound.client.gl.shader.ShaderCompilationException
import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
import ru.dbotthepony.kstarbound.client.render.Box2DRenderer
import ru.dbotthepony.kstarbound.client.render.Font
import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kvector.api.IStruct4f
import ru.dbotthepony.kvector.arrays.Matrix4fStack
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.RGBAColor
import java.io.File
import java.lang.ref.Cleaner
import java.time.Duration
import java.util.*
import java.util.function.IntConsumer
import kotlin.collections.ArrayList
import kotlin.math.roundToInt
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
private class GLStateSwitchTracker(private val enum: Int, private var value: Boolean = false) {
operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): Boolean {
return value
}
operator fun setValue(glStateTracker: GLStateTracker, property: KProperty<*>, value: Boolean) {
glStateTracker.ensureSameThread()
if (value == this.value)
return
if (value) {
glEnable(enum)
} else {
glDisable(enum)
}
checkForGLError()
this.value = value
}
}
private class GLStateIntTracker(private val fn: Function, private val enum: Int, private var value: Int) {
fun interface Function {
fun invoke(enum: Int, value: Int)
}
operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): Int {
return value
}
operator fun setValue(glStateTracker: GLStateTracker, property: KProperty<*>, value: Int) {
glStateTracker.ensureSameThread()
if (value == this.value)
return
fn.invoke(enum, value)
checkForGLError()
this.value = value
}
}
private class GLStateFuncTracker(private val glFunc: IntConsumer, private var value: Int) {
operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): Int {
return value
}
operator fun setValue(glStateTracker: GLStateTracker, property: KProperty<*>, value: Int) {
glStateTracker.ensureSameThread()
if (value == this.value)
return
glFunc.accept(value)
checkForGLError()
this.value = value
}
}
private class GLStateGenericTracker<T>(private var value: T, private val callback: (T) -> Unit) : ReadWriteProperty<GLStateTracker, T> {
override fun getValue(thisRef: GLStateTracker, property: KProperty<*>): T {
return value
}
override fun setValue(thisRef: GLStateTracker, property: KProperty<*>, value: T) {
thisRef.ensureSameThread()
if (value == this.value)
return
callback.invoke(value)
checkForGLError()
this.value = value
}
}
private class TexturesTracker(maxValue: Int) : ReadWriteProperty<GLStateTracker, GLTexture2D?> {
private val values = arrayOfNulls<GLTexture2D>(maxValue)
override fun getValue(thisRef: GLStateTracker, property: KProperty<*>): GLTexture2D? {
return values[thisRef.activeTexture]
}
override fun setValue(thisRef: GLStateTracker, property: KProperty<*>, value: GLTexture2D?) {
thisRef.ensureSameThread()
require(value == null || thisRef === value.state) { "$value does not belong to $thisRef" }
if (values[thisRef.activeTexture] === value) {
return
}
values[thisRef.activeTexture] = value
if (value == null) {
glBindTexture(GL_TEXTURE_2D, 0)
checkForGLError()
return
}
glBindTexture(GL_TEXTURE_2D, value.pointer)
checkForGLError()
}
}
@Suppress("PropertyName", "unused")
class GLStateTracker(val client: StarboundClient) {
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 {
check(TRACKERS.get() == null) { "Already has state tracker existing at this thread!" }
TRACKERS.set(this)
}
// This line is critical for LWJGL's interoperation with GLFW's
// OpenGL context, or any context that is managed externally.
// LWJGL detects the context that is current in the current thread,
// creates the GLCapabilities instance and makes the OpenGL
// bindings available for use.
val capabilities: GLCapabilities = GL.createCapabilities()
val programs = GLPrograms(this)
val flat2DLines by lazy { StreamVertexBuilder(this, GLAttributeList.VEC2F, GeometryType.LINES) }
val flat2DTriangles by lazy { StreamVertexBuilder(this, GLAttributeList.VEC2F, GeometryType.TRIANGLES) }
val flat2DTexturedQuads by lazy { StreamVertexBuilder(this, GLAttributeList.VERTEX_TEXTURE, GeometryType.QUADS) }
val quadWireframe by lazy { StreamVertexBuilder(this, GLAttributeList.VEC2F, GeometryType.QUADS_AS_LINES_WIREFRAME) }
val matrixStack = Matrix4fStack()
val freeType = FreeType()
val font = Font(this)
val thread: Thread = Thread.currentThread()
val box2dRenderer = Box2DRenderer(this)
private val scissorStack = LinkedList<ScissorRect>()
private val cleanerBacklog = ArrayList<() -> Unit>()
@Volatile
var objectsCleaned = 0L
private set
@Volatile
var gcHits = 0L
private set
private val cleaner = Cleaner.create { r ->
val thread = Thread(r, "OpenGL Cleaner for '${thread.name}'")
thread.priority = 2
thread
}
fun registerCleanable(ref: Any, fn: (Int) -> Unit, nativeRef: Int): Cleaner.Cleanable {
val cleanable = cleaner.register(ref) {
objectsCleaned++
if (isSameThread()) {
fn(nativeRef)
checkForGLError()
} else {
gcHits++
synchronized(cleanerBacklog) {
cleanerBacklog.add {
fn(nativeRef)
checkForGLError()
}
}
}
}
return cleanable
}
fun cleanup() {
synchronized(cleanerBacklog) {
for (lambda in cleanerBacklog) {
lambda.invoke()
}
cleanerBacklog.clear()
}
}
var blend by GLStateSwitchTracker(GL_BLEND)
var scissor by GLStateSwitchTracker(GL_SCISSOR_TEST)
var cull by GLStateSwitchTracker(GL_CULL_FACE)
var cullMode by GLStateFuncTracker(::glCullFace, GL_BACK)
var textureUnpackAlignment by GLStateIntTracker(::glPixelStorei, GL_UNPACK_ALIGNMENT, 4)
var scissorRect by GLStateGenericTracker(ScissorRect(0, 0, 0, 0)) {
// require(it.x >= 0) { "Invalid X ${it.x}"}
// require(it.y >= 0) { "Invalid Y ${it.y}"}
require(it.width >= 0) { "Invalid width ${it.width}"}
require(it.height >= 0) { "Invalid height ${it.height}"}
glScissor(it.x, it.y, it.width, it.height)
}
var depthTest by GLStateSwitchTracker(GL_DEPTH_TEST)
var VBO: VertexBufferObject? = null
set(value) {
ensureSameThread()
if (field !== value) {
isMe(value?.state)
require(value?.isArray != false) { "Provided buffer object is not of Array type" }
glBindBuffer(GL_ARRAY_BUFFER, value?.pointer ?: 0)
checkForGLError("Setting Vertex Buffer Object")
field = value
}
}
var EBO: VertexBufferObject? = null
set(value) {
ensureSameThread()
if (field !== value) {
isMe(value?.state)
require(value?.isElementArray != false) { "Provided buffer object is not of Array type" }
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, value?.pointer ?: 0)
checkForGLError("Setting Element Buffer Object")
field = value
}
}
var VAO: VertexArrayObject? = null
set(value) {
ensureSameThread()
if (field !== value) {
isMe(value?.state)
glBindVertexArray(value?.pointer ?: 0)
checkForGLError("Setting Vertex Array Object")
field = value
}
}
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
set(value) {
ensureSameThread()
if (value !== field) {
isMe(value?.state)
glUseProgram(value?.pointer ?: 0)
checkForGLError("Setting shader program")
field = value
}
}
var activeTexture = 0
set(value) {
ensureSameThread()
if (field != value) {
require(value >= 0) { "Invalid texture block $value" }
require(value < 80) { "Too big texture block index $value, OpenGL 4.6 guarantee only 80!" }
glActiveTexture(GL_TEXTURE0 + value)
checkForGLError()
field = value
}
}
var texture2D: GLTexture2D? by TexturesTracker(80)
var clearColor by GLStateGenericTracker<IStruct4f>(RGBAColor.WHITE) {
val (r, g, b, a) = it
glClearColor(r, g, b, a)
}
var blendFunc by GLStateGenericTracker(BlendFunc()) {
glBlendFuncSeparate(it.sourceColor.enum, it.destinationColor.enum, it.sourceAlpha.enum, it.destinationAlpha.enum)
}
init {
glActiveTexture(GL_TEXTURE0)
checkForGLError()
}
var viewportX: Int = 0
private set
var viewportY: Int = 0
private set
var viewportWidth: Int = 0
private set
var viewportHeight: Int = 0
private set
fun setViewport(x: Int, y: Int, width: Int, height: Int) {
ensureSameThread()
if (viewportX != x || viewportY != y || viewportWidth != width || viewportHeight != height) {
glViewport(x, y, width, height)
checkForGLError("Setting viewport")
viewportX = x
viewportY = y
viewportWidth = width
viewportHeight = height
}
}
fun pushScissorRect(x: Float, y: Float, width: Float, height: Float) {
return pushScissorRect(x.roundToInt(), y.roundToInt(), width.roundToInt(), height.roundToInt())
}
@Suppress("NAME_SHADOWING")
fun pushScissorRect(x: Int, y: Int, width: Int, height: Int) {
var x = x
var y = y
var width = width
var height = height
val peek = scissorStack.lastOrNull()
if (peek != null) {
x = x.coerceAtLeast(peek.x)
y = y.coerceAtLeast(peek.y)
width = width.coerceAtMost(peek.width)
height = height.coerceAtMost(peek.height)
if (peek.x == x && peek.y == y && peek.width == width && peek.height == height) {
scissorStack.add(peek)
return
}
}
val rect = ScissorRect(x, y, width, height)
scissorStack.add(rect)
scissorRect = rect
scissor = true
}
fun popScissorRect() {
scissorStack.removeLast()
val peek = scissorStack.lastOrNull()
if (peek == null) {
scissor = false
return
}
val y = viewportHeight - peek.y - peek.height
scissorRect = ScissorRect(peek.x, y, peek.width, peek.height)
}
val currentScissorRect get() = scissorStack.lastOrNull()
fun ensureSameThread() {
if (thread !== Thread.currentThread()) {
throw IllegalAccessException("Trying to access $this outside of $thread!")
}
}
fun isSameThread() = thread === Thread.currentThread()
fun newVBO(type: VBOType = VBOType.ARRAY): VertexBufferObject {
return VertexBufferObject(this, type)
}
fun newEBO() = newVBO(VBOType.ELEMENT_ARRAY)
fun newVAO() = VertexArrayObject(this)
fun newTexture(name: String = "<unknown>") = GLTexture2D(this, name)
// минимальное время хранения 5 минут и...
private val named2DTextures0: Cache<String, GLTexture2D> = Caffeine.newBuilder()
.expireAfterAccess(Duration.ofMinutes(5))
.build()
// ...бесконечное хранение пока кто-то все ещё использует текстуру
private val named2DTextures1: Cache<String, GLTexture2D> = Caffeine.newBuilder()
.weakValues()
.build()
private val missingTexture: GLTexture2D by lazy {
newTexture(missingTexturePath).upload(Starbound.readDirect(missingTexturePath), GL_RGBA, GL_RGBA).generateMips().also {
it.textureMinFilter = GL_NEAREST
it.textureMagFilter = GL_NEAREST
}
}
private val missingTexturePath = "/assetmissing.png"
fun loadTexture(path: String): GLTexture2D {
ensureSameThread()
return named2DTextures0.get(path) {
named2DTextures1.get(it) {
val data = Image.get(it)
if (data == null) {
LOGGER.error("Texture {} is missing! Falling back to {}", it, missingTexturePath)
missingTexture
} else {
newTexture(it).upload(data).also {
it.textureMinFilter = GL_NEAREST
it.textureMagFilter = GL_NEAREST
}
}
}
}
}
fun bind(obj: VertexBufferObject): VertexBufferObject {
if (obj.type == VBOType.ARRAY)
VBO = obj
else
EBO = obj
return obj
}
fun unbind(obj: VertexBufferObject): VertexBufferObject {
if (obj.type == VBOType.ARRAY)
if (obj == VBO)
VBO = null
else
if (obj == EBO)
EBO = null
return obj
}
fun bind(obj: VertexArrayObject): VertexArrayObject {
VAO = obj
return obj
}
fun unbind(obj: VertexArrayObject): VertexArrayObject {
if (obj == VAO)
VAO = null
return obj
}
inline fun quadWireframe(color: RGBAColor = RGBAColor.WHITE, lambda: (VertexBuilder) -> Unit) {
val builder = quadWireframe
builder.builder.begin()
lambda.invoke(builder.builder)
builder.upload()
programs.flat.use()
programs.flat.color = color
programs.flat.transform = matrixStack.last()
builder.draw(GL_LINES)
}
inline fun quadColor(lambda: (VertexBuilder) -> Unit) {
val builder = programs.flatColor.builder
builder.builder.begin()
lambda.invoke(builder.builder)
builder.upload()
programs.flatColor.use()
programs.flatColor.transform = matrixStack.last()
builder.draw(GL_TRIANGLES)
}
inline fun quadTexture(texture: GLTexture2D, lambda: (VertexBuilder) -> Unit) {
val builder = programs.textured2d.builder
builder.builder.begin()
lambda.invoke(builder.builder)
builder.upload()
activeTexture = 0
texture.bind()
programs.textured2d.use()
programs.textured2d.transform = matrixStack.last()
programs.textured2d.texture = 0
builder.draw(GL_TRIANGLES)
}
inline fun quadWireframe(value: AABB, color: RGBAColor = RGBAColor.WHITE, chain: (VertexBuilder) -> Unit = {}) {
quadWireframe(color) {
it.quad(value.mins.x.toFloat(), value.mins.y.toFloat(), value.maxs.x.toFloat(), value.maxs.y.toFloat())
chain(it)
}
}
inner class Shader(body: String, type: Int) {
constructor(body: File, type: Int) : this(body.also { require(it.exists()) { "Shader file does not exists: $body" } }.readText(), type)
init {
ensureSameThread()
}
val pointer = glCreateShader(type)
init {
checkForGLError()
registerCleanable(this, ::glDeleteShader, pointer)
}
init {
if (body == "") {
throw IllegalArgumentException("Shader source is empty")
}
glShaderSource(pointer, body)
glCompileShader(pointer)
val result = intArrayOf(0)
glGetShaderiv(pointer, GL_COMPILE_STATUS, result)
if (result[0] == 0) {
throw ShaderCompilationException(glGetShaderInfoLog(pointer))
}
checkForGLError()
}
}
fun vertex(file: File) = Shader(file, GL_VERTEX_SHADER)
fun fragment(file: File) = Shader(file, GL_FRAGMENT_SHADER)
fun vertex(contents: String) = Shader(contents, GL_VERTEX_SHADER)
fun fragment(contents: String) = Shader(contents, GL_FRAGMENT_SHADER)
fun internalVertex(file: String) = Shader(readInternal(file), GL_VERTEX_SHADER)
fun internalFragment(file: String) = Shader(readInternal(file), GL_FRAGMENT_SHADER)
fun internalGeometry(file: String) = Shader(readInternal(file), GL_GEOMETRY_SHADER)
companion object {
private val LOGGER = LogManager.getLogger(GLStateTracker::class.java)
private val TRACKERS = ThreadLocal<GLStateTracker>()
fun current() = checkNotNull(TRACKERS.get()) { "Current thread has no OpenGL State attached" }
fun currentOrNull(): GLStateTracker? = TRACKERS.get()
private fun readInternal(file: String): String {
return ClassLoader.getSystemClassLoader().getResourceAsStream(file)!!.bufferedReader()
.let {
val read = it.readText()
it.close()
read
}
}
}
}

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.client.gl
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.lwjgl.opengl.GL46.* import org.lwjgl.opengl.GL46.*
import org.lwjgl.stb.STBImage import org.lwjgl.stb.STBImage
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.defs.image.Image import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kvector.vector.Vector2i import ru.dbotthepony.kvector.vector.Vector2i
import java.io.File import java.io.File
@ -20,7 +21,7 @@ private class GLTexturePropertyTracker(private val flag: Int, private var value:
} }
operator fun setValue(thisRef: GLTexture2D, property: KProperty<*>, value: Int) { operator fun setValue(thisRef: GLTexture2D, property: KProperty<*>, value: Int) {
thisRef.state.ensureSameThread() thisRef.client.ensureSameThread()
if (this.value == value) return if (this.value == value) return
this.value = value this.value = value
glTextureParameteri(thisRef.pointer, flag, value) glTextureParameteri(thisRef.pointer, flag, value)
@ -29,16 +30,13 @@ private class GLTexturePropertyTracker(private val flag: Int, private var value:
} }
@Suppress("SameParameterValue") @Suppress("SameParameterValue")
class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") { class GLTexture2D(val name: String = "<unknown>") {
init { val client = StarboundClient.current()
state.ensureSameThread()
}
val pointer = glGenTextures() val pointer = glGenTextures()
init { init {
checkForGLError() checkForGLError()
state.registerCleanable(this, ::glDeleteTextures, pointer) client.registerCleanable(this, ::glDeleteTextures, pointer)
} }
var width = 0 var width = 0
@ -74,12 +72,12 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") {
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 {
state.texture2D = this client.texture2D = this
return this return this
} }
fun generateMips(): GLTexture2D { fun generateMips(): GLTexture2D {
state.ensureSameThread() client.ensureSameThread()
glGenerateTextureMipmap(pointer) glGenerateTextureMipmap(pointer)
checkForGLError("Generating texture mipmaps") checkForGLError("Generating texture mipmaps")
return this return this
@ -150,7 +148,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") {
} }
fun upload(path: File, memoryFormat: Int, bufferFormat: Int): GLTexture2D { fun upload(path: File, memoryFormat: Int, bufferFormat: Int): GLTexture2D {
state.ensureSameThread() client.ensureSameThread()
if (!path.exists()) { if (!path.exists()) {
throw FileNotFoundException("${path.absolutePath} does not exist") throw FileNotFoundException("${path.absolutePath} does not exist")
@ -176,7 +174,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") {
} }
fun upload(path: File): GLTexture2D { fun upload(path: File): GLTexture2D {
state.ensureSameThread() client.ensureSameThread()
if (!path.exists()) { if (!path.exists()) {
throw FileNotFoundException("${path.absolutePath} does not exist") throw FileNotFoundException("${path.absolutePath} does not exist")
@ -210,7 +208,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") {
} }
fun upload(buff: ByteBuffer, memoryFormat: Int, bufferFormat: Int): GLTexture2D { fun upload(buff: ByteBuffer, memoryFormat: Int, bufferFormat: Int): GLTexture2D {
state.ensureSameThread() client.ensureSameThread()
val getwidth = intArrayOf(0) val getwidth = intArrayOf(0)
val getheight = intArrayOf(0) val getheight = intArrayOf(0)
@ -228,7 +226,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") {
} }
fun upload(buff: ByteBuffer): GLTexture2D { fun upload(buff: ByteBuffer): GLTexture2D {
state.ensureSameThread() client.ensureSameThread()
val getwidth = intArrayOf(0) val getwidth = intArrayOf(0)
val getheight = intArrayOf(0) val getheight = intArrayOf(0)
@ -254,7 +252,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") {
} }
fun upload(data: Image): GLTexture2D { fun upload(data: Image): GLTexture2D {
state.ensureSameThread() client.ensureSameThread()
val bufferFormat = when (val numChannels = data.amountOfChannels) { val bufferFormat = when (val numChannels = data.amountOfChannels) {
1 -> GL_R 1 -> GL_R

View File

@ -16,4 +16,4 @@ data class ScissorRect(val x: Int, val y: Int, val width: Int, val height: Int)
this.x + this.width, this.y + this.height, this.x + this.width, this.y + this.height,
) )
} }
} }

View File

@ -1,32 +1,34 @@
package ru.dbotthepony.kstarbound.client.gl package ru.dbotthepony.kstarbound.client.gl
import org.lwjgl.opengl.GL46.* import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.client.StarboundClient
class VertexArrayObject(val state: GLStateTracker) { class VertexArrayObject {
val client = StarboundClient.current()
val pointer = glGenVertexArrays() val pointer = glGenVertexArrays()
init { init {
checkForGLError() checkForGLError()
state.registerCleanable(this, ::glDeleteVertexArrays, pointer) client.registerCleanable(this, ::glDeleteVertexArrays, pointer)
} }
fun bind(): VertexArrayObject { fun bind(): VertexArrayObject {
return state.bind(this) return client.bind(this)
} }
fun unbind(): VertexArrayObject { fun unbind(): VertexArrayObject {
return state.unbind(this) return client.unbind(this)
} }
fun attribute(position: Int, size: Int, type: Int, normalize: Boolean, stride: Int, offset: Long = 0L): VertexArrayObject { fun attribute(position: Int, size: Int, type: Int, normalize: Boolean, stride: Int, offset: Long = 0L): VertexArrayObject {
state.ensureSameThread() client.ensureSameThread()
glVertexAttribPointer(position, size, type, normalize, stride, offset) glVertexAttribPointer(position, size, type, normalize, stride, offset)
checkForGLError() checkForGLError()
return this return this
} }
fun enableAttribute(position: Int): VertexArrayObject { fun enableAttribute(position: Int): VertexArrayObject {
state.ensureSameThread() client.ensureSameThread()
glEnableVertexArrayAttrib(pointer, position) glEnableVertexArrayAttrib(pointer, position)
//glEnableVertexAttribArray(position) //glEnableVertexAttribArray(position)
checkForGLError() checkForGLError()

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.client.gl
import org.lwjgl.opengl.GL46.* import org.lwjgl.opengl.GL46.*
import org.lwjgl.system.MemoryUtil import org.lwjgl.system.MemoryUtil
import ru.dbotthepony.kstarbound.client.StarboundClient
import java.nio.ByteBuffer import java.nio.ByteBuffer
enum class VBOType(val glType: Int) { enum class VBOType(val glType: Int) {
@ -9,36 +10,37 @@ enum class VBOType(val glType: Int) {
ELEMENT_ARRAY(GL_ELEMENT_ARRAY_BUFFER), ELEMENT_ARRAY(GL_ELEMENT_ARRAY_BUFFER),
} }
class VertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOType.ARRAY) { class VertexBufferObject private constructor(val type: VBOType) {
val client = StarboundClient.current()
val pointer = glGenBuffers() val pointer = glGenBuffers()
init { init {
checkForGLError("Creating Vertex Buffer Object") checkForGLError("Creating Vertex Buffer Object")
state.registerCleanable(this, ::glDeleteBuffers, pointer) client.registerCleanable(this, ::glDeleteBuffers, pointer)
} }
val isArray get() = type == VBOType.ARRAY val isArray get() = type == VBOType.ARRAY
val isElementArray get() = type == VBOType.ELEMENT_ARRAY val isElementArray get() = type == VBOType.ELEMENT_ARRAY
fun bind(): VertexBufferObject { fun bind(): VertexBufferObject {
state.bind(this) client.bind(this)
return this return this
} }
fun unbind(): VertexBufferObject { fun unbind(): VertexBufferObject {
state.unbind(this) client.unbind(this)
return this return this
} }
fun bufferData(data: ByteBuffer, usage: Int): VertexBufferObject { fun bufferData(data: ByteBuffer, usage: Int): VertexBufferObject {
state.ensureSameThread() client.ensureSameThread()
glNamedBufferData(pointer, data, usage) glNamedBufferData(pointer, data, usage)
checkForGLError() checkForGLError()
return this return this
} }
fun bufferData(data: ByteBuffer, usage: Int, length: Long): VertexBufferObject { fun bufferData(data: ByteBuffer, usage: Int, length: Long): VertexBufferObject {
state.ensureSameThread() client.ensureSameThread()
if (length > data.remaining().toLong()) { if (length > data.remaining().toLong()) {
throw IndexOutOfBoundsException("Tried to upload $data into $pointer with offset at ${data.position()} with length of $length, but that is longer than remaining data length of ${data.remaining()}!") throw IndexOutOfBoundsException("Tried to upload $data into $pointer with offset at ${data.position()} with length of $length, but that is longer than remaining data length of ${data.remaining()}!")
@ -51,30 +53,35 @@ class VertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOType.
} }
fun bufferData(data: IntArray, usage: Int): VertexBufferObject { fun bufferData(data: IntArray, usage: Int): VertexBufferObject {
state.ensureSameThread() client.ensureSameThread()
glNamedBufferData(pointer, data, usage) glNamedBufferData(pointer, data, usage)
checkForGLError() checkForGLError()
return this return this
} }
fun bufferData(data: FloatArray, usage: Int): VertexBufferObject { fun bufferData(data: FloatArray, usage: Int): VertexBufferObject {
state.ensureSameThread() client.ensureSameThread()
glNamedBufferData(pointer, data, usage) glNamedBufferData(pointer, data, usage)
checkForGLError() checkForGLError()
return this return this
} }
fun bufferData(data: DoubleArray, usage: Int): VertexBufferObject { fun bufferData(data: DoubleArray, usage: Int): VertexBufferObject {
state.ensureSameThread() client.ensureSameThread()
glNamedBufferData(pointer, data, usage) glNamedBufferData(pointer, data, usage)
checkForGLError() checkForGLError()
return this return this
} }
fun bufferData(data: LongArray, usage: Int): VertexBufferObject { fun bufferData(data: LongArray, usage: Int): VertexBufferObject {
state.ensureSameThread() client.ensureSameThread()
glNamedBufferData(pointer, data, usage) glNamedBufferData(pointer, data, usage)
checkForGLError() checkForGLError()
return this return this
} }
companion object {
fun vbo() = VertexBufferObject(VBOType.ARRAY)
fun ebo() = VertexBufferObject(VBOType.ELEMENT_ARRAY)
}
} }

View File

@ -0,0 +1,23 @@
package ru.dbotthepony.kstarbound.client.gl.properties
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import java.util.function.IntConsumer
import kotlin.reflect.KProperty
class GLStateFuncTracker(private val glFunc: IntConsumer, private var value: Int) {
operator fun getValue(glStateTracker: StarboundClient, property: KProperty<*>): Int {
return value
}
operator fun setValue(glStateTracker: StarboundClient, property: KProperty<*>, value: Int) {
glStateTracker.ensureSameThread()
if (value == this.value)
return
glFunc.accept(value)
checkForGLError()
this.value = value
}
}

View File

@ -0,0 +1,23 @@
package ru.dbotthepony.kstarbound.client.gl.properties
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class GLStateGenericTracker<T>(private var value: T, private val callback: (T) -> Unit) : ReadWriteProperty<StarboundClient, T> {
override fun getValue(thisRef: StarboundClient, property: KProperty<*>): T {
return value
}
override fun setValue(thisRef: StarboundClient, property: KProperty<*>, value: T) {
thisRef.ensureSameThread()
if (value == this.value)
return
callback.invoke(value)
checkForGLError()
this.value = value
}
}

View File

@ -0,0 +1,26 @@
package ru.dbotthepony.kstarbound.client.gl.properties
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import kotlin.reflect.KProperty
class GLStateIntTracker(private val fn: Function, private val enum: Int, private var value: Int) {
fun interface Function {
fun invoke(enum: Int, value: Int)
}
operator fun getValue(glStateTracker: StarboundClient, property: KProperty<*>): Int {
return value
}
operator fun setValue(glStateTracker: StarboundClient, property: KProperty<*>, value: Int) {
glStateTracker.ensureSameThread()
if (value == this.value)
return
fn.invoke(enum, value)
checkForGLError()
this.value = value
}
}

View File

@ -0,0 +1,29 @@
package ru.dbotthepony.kstarbound.client.gl.properties
import org.lwjgl.opengl.GL11.glDisable
import org.lwjgl.opengl.GL11.glEnable
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import kotlin.reflect.KProperty
class GLStateSwitchTracker(private val enum: Int, private var value: Boolean = false) {
operator fun getValue(glStateTracker: StarboundClient, property: KProperty<*>): Boolean {
return value
}
operator fun setValue(glStateTracker: StarboundClient, property: KProperty<*>, value: Boolean) {
glStateTracker.ensureSameThread()
if (value == this.value)
return
if (value) {
glEnable(enum)
} else {
glDisable(enum)
}
checkForGLError()
this.value = value
}
}

View File

@ -0,0 +1,38 @@
package ru.dbotthepony.kstarbound.client.gl.properties
import org.lwjgl.opengl.GL11.GL_TEXTURE_2D
import org.lwjgl.opengl.GL11.glBindTexture
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class TexturesTracker(maxValue: Int) : ReadWriteProperty<StarboundClient, GLTexture2D?> {
private val values = arrayOfNulls<GLTexture2D>(maxValue)
override fun getValue(thisRef: StarboundClient, property: KProperty<*>): GLTexture2D? {
return values[thisRef.activeTexture]
}
override fun setValue(thisRef: StarboundClient, property: KProperty<*>, value: GLTexture2D?) {
thisRef.ensureSameThread()
require(value == null || thisRef === value.client) { "$value does not belong to $thisRef" }
if (values[thisRef.activeTexture] === value) {
return
}
values[thisRef.activeTexture] = value
if (value == null) {
glBindTexture(GL_TEXTURE_2D, 0)
checkForGLError()
return
}
glBindTexture(GL_TEXTURE_2D, value.pointer)
checkForGLError()
}
}

View File

@ -0,0 +1,41 @@
package ru.dbotthepony.kstarbound.client.gl.shader
import org.lwjgl.opengl.GL20.GL_COMPILE_STATUS
import org.lwjgl.opengl.GL20.glCompileShader
import org.lwjgl.opengl.GL20.glCreateShader
import org.lwjgl.opengl.GL20.glDeleteShader
import org.lwjgl.opengl.GL20.glGetShaderInfoLog
import org.lwjgl.opengl.GL20.glGetShaderiv
import org.lwjgl.opengl.GL20.glShaderSource
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import java.io.File
class GLShader(body: String, type: Int) {
constructor(body: File, type: Int) : this(body.also { require(it.exists()) { "Shader file does not exists: $body" } }.readText(), type)
val client = StarboundClient.current()
val pointer = glCreateShader(type)
init {
checkForGLError()
client.registerCleanable(this, ::glDeleteShader, pointer)
}
init {
if (body == "") {
throw IllegalArgumentException("Shader source is empty")
}
glShaderSource(pointer, body)
glCompileShader(pointer)
val result = intArrayOf(0)
glGetShaderiv(pointer, GL_COMPILE_STATUS, result)
if (result[0] == 0) {
throw ShaderCompilationException(glGetShaderInfoLog(pointer))
}
checkForGLError()
}
}

View File

@ -5,7 +5,7 @@ import it.unimi.dsi.fastutil.objects.Object2BooleanFunction
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import org.lwjgl.opengl.GL46.* import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.checkForGLError import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList
import ru.dbotthepony.kvector.api.IStruct2f import ru.dbotthepony.kvector.api.IStruct2f
@ -24,19 +24,16 @@ import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
open class GLShaderProgram( open class GLShaderProgram(
val state: GLStateTracker, shaders: Iterable<GLShader>,
shaders: Iterable<GLStateTracker.Shader>,
val attributes: GLAttributeList val attributes: GLAttributeList
) { ) {
init { val client = StarboundClient.current()
state.ensureSameThread()
}
val pointer = glCreateProgram() val pointer = glCreateProgram()
init { init {
checkForGLError("Creating shader program") checkForGLError("Creating shader program")
state.registerCleanable(this, ::glDeleteProgram, pointer) client.registerCleanable(this, ::glDeleteProgram, pointer)
for (shader in shaders) { for (shader in shaders) {
glAttachShader(pointer, shader.pointer) glAttachShader(pointer, shader.pointer)
@ -56,7 +53,7 @@ open class GLShaderProgram(
} }
fun use(): GLShaderProgram { fun use(): GLShaderProgram {
state.program = this client.program = this
return this return this
} }
@ -64,7 +61,7 @@ open class GLShaderProgram(
private val uniformsExist = Object2BooleanArrayMap<String>() private val uniformsExist = Object2BooleanArrayMap<String>()
fun isUniformPresent(name: String): Boolean { fun isUniformPresent(name: String): Boolean {
state.ensureSameThread() client.ensureSameThread()
return uniformsExist.computeIfAbsent(name, Object2BooleanFunction { glGetUniformLocation(pointer, name) != -1 }) return uniformsExist.computeIfAbsent(name, Object2BooleanFunction { glGetUniformLocation(pointer, name) != -1 })
} }
@ -78,7 +75,7 @@ open class GLShaderProgram(
abstract inner class Uniform<V : Any>(val name: String) : ReadWriteProperty<Any?, V> { abstract inner class Uniform<V : Any>(val name: String) : ReadWriteProperty<Any?, V> {
init { init {
state.ensureSameThread() client.ensureSameThread()
require(!locations.containsKey(name)) { "Already has uniform $name for ${this@GLShaderProgram} (${locations[name]})" } require(!locations.containsKey(name)) { "Already has uniform $name for ${this@GLShaderProgram} (${locations[name]})" }
} }
@ -110,7 +107,7 @@ open class GLShaderProgram(
inner class FUniform(name: String) : Uniform<Float>(name) { inner class FUniform(name: String) : Uniform<Float>(name) {
override var value: Float = 0f override var value: Float = 0f
set(value) { set(value) {
state.ensureSameThread() client.ensureSameThread()
if (field != value) { if (field != value) {
glProgramUniform1f(pointer, location, value) glProgramUniform1f(pointer, location, value)
@ -135,7 +132,7 @@ open class GLShaderProgram(
override var value: IStruct2f = Vector2f.ZERO override var value: IStruct2f = Vector2f.ZERO
set(value) { set(value) {
state.ensureSameThread() client.ensureSameThread()
val (v0, v1) = value val (v0, v1) = value
@ -157,7 +154,7 @@ open class GLShaderProgram(
override var value: IStruct3f = Vector3f.ZERO override var value: IStruct3f = Vector3f.ZERO
set(value) { set(value) {
state.ensureSameThread() client.ensureSameThread()
val (v0, v1, v2) = value val (v0, v1, v2) = value
@ -179,7 +176,7 @@ open class GLShaderProgram(
inner class F3x3Uniform(name: String) : Uniform<Matrix3f>(name) { inner class F3x3Uniform(name: String) : Uniform<Matrix3f>(name) {
override var value: Matrix3f = Matrix3f.zero() override var value: Matrix3f = Matrix3f.zero()
set(value) { set(value) {
state.ensureSameThread() client.ensureSameThread()
if (field != value) { if (field != value) {
buff3x3.position(0) buff3x3.position(0)
@ -198,7 +195,7 @@ open class GLShaderProgram(
override var value: Matrix4f = Matrix4f.zero() override var value: Matrix4f = Matrix4f.zero()
set(value) { set(value) {
state.ensureSameThread() client.ensureSameThread()
buff4x4.position(0) buff4x4.position(0)
value.storeRowColumn(buff4x4) value.storeRowColumn(buff4x4)
@ -223,7 +220,7 @@ open class GLShaderProgram(
override var value: IStruct4f = Vector4f.ZERO override var value: IStruct4f = Vector4f.ZERO
set(value) { set(value) {
state.ensureSameThread() client.ensureSameThread()
val (v0, v1, v2, v3) = value val (v0, v1, v2, v3) = value
@ -243,7 +240,7 @@ open class GLShaderProgram(
inner class IUniform(name: String) : Uniform<Int>(name) { inner class IUniform(name: String) : Uniform<Int>(name) {
override var value: Int = 0 override var value: Int = 0
set(value) { set(value) {
state.ensureSameThread() client.ensureSameThread()
if (field != value) { if (field != value) {
glProgramUniform1i(pointer, location, value) glProgramUniform1i(pointer, location, value)

View File

@ -1,33 +1,27 @@
package ru.dbotthepony.kstarbound.client.gl.shader package ru.dbotthepony.kstarbound.client.gl.shader
import ru.dbotthepony.kstarbound.client.gl.BlendFunc import ru.dbotthepony.kstarbound.client.StarboundClient
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.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.GeometryType import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kvector.arrays.Matrix4f import ru.dbotthepony.kvector.arrays.Matrix4f
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
private fun GLStateTracker.shaders(name: String): List<GLStateTracker.Shader> { private fun internalVertex(string: String) = StarboundClient.current().internalVertex(string)
return listOf(internalVertex("shaders/$name.vsh"), internalFragment("shaders/$name.fsh")) private fun internalFragment(string: String) = StarboundClient.current().internalFragment(string)
private fun shaders(name: String): List<GLShader> {
val client = StarboundClient.current()
return listOf(client.internalVertex("shaders/$name.vsh"), client.internalFragment("shaders/$name.fsh"))
} }
private fun GLStateTracker.gshaders(name: String): List<GLStateTracker.Shader> { class GLLiquidProgram : GLShaderProgram(shaders("liquid"), FORMAT) {
return listOf(
internalVertex(name),
internalFragment(name),
internalGeometry(name)
)
}
class GLLiquidProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("liquid"), FORMAT) {
var baselineColor by F4Uniform("baselineColor") var baselineColor by F4Uniform("baselineColor")
var transform by F4x4Uniform("transform") var transform by F4x4Uniform("transform")
val builder by lazy { val builder by lazy {
StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 16384) StreamVertexBuilder(FORMAT, GeometryType.QUADS, 16384)
} }
companion object { companion object {
@ -35,118 +29,11 @@ class GLLiquidProgram(state: GLStateTracker) : GLShaderProgram(state, state.shad
} }
} }
class GLLightProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("light"), FORMAT) { class GLFlatColorProgram : GLShaderProgram(shaders("flat_color"), FORMAT) {
var baselineColor by F4Uniform("baselineColor")
var transform by F4x4Uniform("transform") var transform by F4x4Uniform("transform")
val builder by lazy { val builder by lazy {
StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 32) StreamVertexBuilder(FORMAT, GeometryType.QUADS, 16384)
}
companion object {
val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).push(GLType.VEC2F).build()
}
}
class GLColorQuadProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("screen_quad"), FORMAT) {
var color by F4Uniform("color")
private val builder by lazy {
val builder = StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 1)
builder.builder.begin()
builder.builder.quad(-1f, -1f, 1f, 1f)
builder.upload()
builder
}
fun clearAlpha() {
use()
color = 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: RGBAColor = RGBAColor.WHITE) {
use()
this.color = 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 = RGBAColor(0f, 0f, 0f, 1f)
}
}
class GLTextureQuadProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("screen_quad_tex"), FORMAT) {
var texture by IUniform("texture0")
private val builder by lazy {
val builder = StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 1)
builder.builder.begin()
builder.builder.quad(-1f, -1f, 1f, 1f, QuadTransformers.uv())
builder.upload()
builder
}
fun run(texture: Int = 0) {
use()
this.texture = texture
builder.draw()
}
companion object {
val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).push(GLType.VEC2F).build()
}
}
class GLTextureBlurredQuadProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("screen_quad_tex_blur"), FORMAT) {
var texture by IUniform("texture0")
private val builder by lazy {
val builder = StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 1)
builder.builder.begin()
builder.builder.quad(-1f, -1f, 1f, 1f, QuadTransformers.uv())
builder.upload()
builder
}
fun run(texture: Int = 0) {
use()
this.texture = texture
builder.draw()
}
companion object {
val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).push(GLType.VEC2F).build()
}
}
class GLFlatColorProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("flat_color"), FORMAT) {
var transform by F4x4Uniform("transform")
val builder by lazy {
StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 16384)
} }
companion object { companion object {
@ -154,7 +41,7 @@ class GLFlatColorProgram(state: GLStateTracker) : GLShaderProgram(state, state.s
} }
} }
class GLTileProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("tile"), FORMAT) { class GLTileProgram : GLShaderProgram(shaders("tile"), FORMAT) {
var transform by F4x4Uniform("transform") var transform by F4x4Uniform("transform")
var color by F4Uniform("color") var color by F4Uniform("color")
var texture by IUniform("texture0") var texture by IUniform("texture0")
@ -169,7 +56,7 @@ class GLTileProgram(state: GLStateTracker) : GLShaderProgram(state, state.shader
} }
} }
class GLFontProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("font"), GLAttributeList.VERTEX_2D_TEXTURE) { class GLFontProgram : GLShaderProgram(shaders("font"), GLAttributeList.VERTEX_2D_TEXTURE) {
var transform by F4x4Uniform("transform") var transform by F4x4Uniform("transform")
var color by F4Uniform("color") var color by F4Uniform("color")
var texture by IUniform("texture0") var texture by IUniform("texture0")
@ -180,7 +67,7 @@ class GLFontProgram(state: GLStateTracker) : GLShaderProgram(state, state.shader
} }
} }
class GLFlatProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("flat"), GLAttributeList.VEC2F) { class GLFlatProgram : GLShaderProgram(shaders("flat"), GLAttributeList.VEC2F) {
var transform by F4x4Uniform("transform") var transform by F4x4Uniform("transform")
var color by F4Uniform("color") var color by F4Uniform("color")
@ -189,12 +76,12 @@ class GLFlatProgram(state: GLStateTracker) : GLShaderProgram(state, state.shader
} }
} }
class GLTexturedProgram(state: GLStateTracker) : GLShaderProgram(state, listOf(state.internalVertex("shaders/vertex/texture.glsl"), state.internalFragment("shaders/fragment/texture.glsl")), GLAttributeList.VERTEX_TEXTURE) { class GLTexturedProgram : GLShaderProgram(listOf(internalVertex("shaders/vertex/texture.glsl"), internalFragment("shaders/fragment/texture.glsl")), GLAttributeList.VERTEX_TEXTURE) {
var transform by F4x4Uniform("transform") var transform by F4x4Uniform("transform")
var texture by IUniform("texture0") var texture by IUniform("texture0")
val builder by lazy { val builder by lazy {
StreamVertexBuilder(state, GLAttributeList.Builder().push(GLType.VEC3F, GLType.VEC2F).build(), GeometryType.QUADS, 16384) StreamVertexBuilder(GLAttributeList.Builder().push(GLType.VEC3F, GLType.VEC2F).build(), GeometryType.QUADS, 16384)
} }
init { init {
@ -202,12 +89,12 @@ class GLTexturedProgram(state: GLStateTracker) : GLShaderProgram(state, listOf(s
} }
} }
class GLTextured2dProgram(state: GLStateTracker) : GLShaderProgram(state, listOf(state.internalVertex("shaders/vertex/2dtexture.glsl"), state.internalFragment("shaders/fragment/texture.glsl")), GLAttributeList.VERTEX_TEXTURE) { class GLTextured2dProgram : GLShaderProgram(listOf(internalVertex("shaders/vertex/2dtexture.glsl"), internalFragment("shaders/fragment/texture.glsl")), GLAttributeList.VERTEX_TEXTURE) {
var transform by F4x4Uniform("transform") var transform by F4x4Uniform("transform")
var texture by IUniform("texture0") var texture by IUniform("texture0")
val builder by lazy { val builder by lazy {
StreamVertexBuilder(state, GLAttributeList.Builder().push(GLType.VEC2F, GLType.VEC2F).build(), GeometryType.QUADS, 16384) StreamVertexBuilder(GLAttributeList.Builder().push(GLType.VEC2F, GLType.VEC2F).build(), GeometryType.QUADS, 16384)
} }
init { init {
@ -215,7 +102,7 @@ class GLTextured2dProgram(state: GLStateTracker) : GLShaderProgram(state, listOf
} }
} }
class GLTexturedColoredProgram(state: GLStateTracker) : GLShaderProgram(state, listOf(state.internalVertex("shaders/vertex/texture.glsl"), state.internalFragment("shaders/fragment/texture_color.glsl")), GLAttributeList.VERTEX_TEXTURE) { class GLTexturedColoredProgram : GLShaderProgram(listOf(internalVertex("shaders/vertex/texture.glsl"), internalFragment("shaders/fragment/texture_color.glsl")), GLAttributeList.VERTEX_TEXTURE) {
var transform by F4x4Uniform("transform") var transform by F4x4Uniform("transform")
var texture by IUniform("texture0") var texture by IUniform("texture0")
var color by F4Uniform("color") var color by F4Uniform("color")
@ -226,18 +113,13 @@ class GLTexturedColoredProgram(state: GLStateTracker) : GLShaderProgram(state, l
} }
} }
class GLPrograms(val state: GLStateTracker) { class GLPrograms {
val tile by lazy { GLTileProgram(state) } val tile = GLTileProgram()
val font by lazy { GLFontProgram(state) } val font = GLFontProgram()
val flat by lazy { GLFlatProgram(state) } val flat = GLFlatProgram()
val flatColor by lazy { GLFlatColorProgram(state) } val flatColor = GLFlatColorProgram()
val liquid by lazy { GLLiquidProgram(state) } val liquid = GLLiquidProgram()
val light by lazy { GLLightProgram(state) } val textured = GLTexturedProgram()
val textured by lazy { GLTexturedProgram(state) } val textured2d = GLTextured2dProgram()
val textured2d by lazy { GLTextured2dProgram(state) } val texturedColored = GLTexturedColoredProgram()
val texturedColored by lazy { GLTexturedColoredProgram(state) }
val viewColorQuad by lazy { GLColorQuadProgram(state) }
val viewTextureQuad by lazy { GLTextureQuadProgram(state) }
val viewTextureBlurQuad by lazy { GLTextureBlurredQuadProgram(state) }
} }

View File

@ -1,22 +1,24 @@
package ru.dbotthepony.kstarbound.client.gl.vertex package ru.dbotthepony.kstarbound.client.gl.vertex
import org.lwjgl.opengl.GL46 import org.lwjgl.opengl.GL46
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.VertexArrayObject
import ru.dbotthepony.kstarbound.client.gl.VertexBufferObject
import ru.dbotthepony.kstarbound.client.gl.checkForGLError import ru.dbotthepony.kstarbound.client.gl.checkForGLError
/** /**
* Быстрое наполнение буфера вершинами, загрузка в память видеокарты, и отрисовка * Быстрое наполнение буфера вершинами, загрузка в память видеокарты, и отрисовка
*/ */
class StreamVertexBuilder( class StreamVertexBuilder(
val state: GLStateTracker,
attributes: GLAttributeList, attributes: GLAttributeList,
type: GeometryType, type: GeometryType,
initialCapacity: Int = 64, initialCapacity: Int = 64,
) { ) {
val state = StarboundClient.current()
val builder = VertexBuilder(attributes, type, initialCapacity) val builder = VertexBuilder(attributes, type, initialCapacity)
private val vao = state.newVAO() private val vao = VertexArrayObject()
private val vbo = state.newVBO() private val vbo = VertexBufferObject.vbo()
private val ebo = state.newEBO() private val ebo = VertexBufferObject.ebo()
init { init {
vao.bind() vao.bind()

View File

@ -4,13 +4,14 @@ import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kbox2d.api.IDebugDraw import ru.dbotthepony.kbox2d.api.IDebugDraw
import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2d
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
class Box2DRenderer(val state: GLStateTracker) : IDebugDraw { class Box2DRenderer : IDebugDraw {
val state = StarboundClient.current()
override var drawShapes: Boolean = false override var drawShapes: Boolean = false
override var drawJoints: Boolean = false override var drawJoints: Boolean = false
override var drawAABB: Boolean = false override var drawAABB: Boolean = false

View File

@ -28,18 +28,6 @@ class Camera(val client: StarboundClient) {
GLFW_KEY_RIGHT -> pressedRight = action > 0 GLFW_KEY_RIGHT -> pressedRight = action > 0
GLFW_KEY_UP -> pressedUp = action > 0 GLFW_KEY_UP -> pressedUp = action > 0
GLFW_KEY_DOWN -> pressedDown = action > 0 GLFW_KEY_DOWN -> pressedDown = action > 0
GLFW_KEY_C -> {
if (action > 0) {
client.settings.debugCollisions = !client.settings.debugCollisions
if (client.settings.debugCollisions) {
client.putDebugLog("Enable collsion draw")
} else {
client.putDebugLog("Disable collsion draw")
}
}
}
} }
} }

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.client.render
import it.unimi.dsi.fastutil.chars.Char2ObjectArrayMap import it.unimi.dsi.fastutil.chars.Char2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import org.lwjgl.opengl.GL46.* import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.freetype.LoadFlag import ru.dbotthepony.kstarbound.client.freetype.LoadFlag
import ru.dbotthepony.kstarbound.client.gl.* import ru.dbotthepony.kstarbound.client.gl.*
import ru.dbotthepony.kstarbound.client.freetype.struct.FT_Pixel_Mode import ru.dbotthepony.kstarbound.client.freetype.struct.FT_Pixel_Mode
@ -56,10 +57,10 @@ enum class TextAlignY {
} }
class Font( class Font(
val state: GLStateTracker,
val font: String = "hobo.ttf", val font: String = "hobo.ttf",
val size: Int = 48 val size: Int = 48
) { ) {
val state = StarboundClient.current()
val face = state.freeType.Face(font, 0L) val face = state.freeType.Face(font, 0L)
init { init {

View File

@ -1,17 +1,19 @@
package ru.dbotthepony.kstarbound.client.render package ru.dbotthepony.kstarbound.client.render
import org.lwjgl.opengl.GL46 import org.lwjgl.opengl.GL46
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.checkForGLError import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
import ru.dbotthepony.kvector.arrays.Matrix4f import ru.dbotthepony.kvector.arrays.Matrix4f
class Mesh(state: GLStateTracker) { class Mesh() {
constructor(state: GLStateTracker, builder: VertexBuilder) : this(state) { constructor(builder: VertexBuilder) : this() {
load(builder, GL46.GL_STATIC_DRAW) load(builder, GL46.GL_STATIC_DRAW)
} }
val state = StarboundClient.current()
val vao = state.newVAO() val vao = state.newVAO()
val vbo = state.newVBO() val vbo = state.newVBO()
val ebo = state.newEBO() val ebo = state.newEBO()
@ -45,8 +47,8 @@ class Mesh(state: GLStateTracker) {
} }
} }
data class ConfiguredMesh<T : GLShaderProgram>(val config: RenderConfig<T>, val mesh: Mesh = Mesh(config.state)) { data class ConfiguredMesh<T : GLShaderProgram>(val config: RenderConfig<T>, val mesh: Mesh = Mesh()) {
fun render(transform: Matrix4f = config.state.matrixStack.last()) { fun render(transform: Matrix4f = config.client.matrixStack.last()) {
config.setup(transform) config.setup(transform)
mesh.render() mesh.render()
config.uninstall() config.uninstall()

View File

@ -4,10 +4,10 @@ import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram
import ru.dbotthepony.kvector.arrays.Matrix4f import ru.dbotthepony.kvector.arrays.Matrix4f
abstract class RenderConfig<out T : GLShaderProgram>(val program: T) { abstract class RenderConfig<out T : GLShaderProgram>(val program: T) {
val state get() = program.state val client get() = program.client
open val initialBuilderCapacity: Int get() = 64 open val initialBuilderCapacity: Int get() = 64
open fun setup(transform: Matrix4f = state.matrixStack.last()) { open fun setup(transform: Matrix4f = client.matrixStack.last()) {
program.use() program.use()
} }

View File

@ -20,11 +20,9 @@ import kotlin.collections.HashMap
/** /**
* Хранит в себе программы для отрисовки определённых [TileDefinition] * Хранит в себе программы для отрисовки определённых [TileDefinition]
* *
* Создаётся единожды как потомок [GLStateTracker] * Создаётся единожды как потомок [Graphics]
*/ */
class TileRenderers(val client: StarboundClient) { class TileRenderers(val client: StarboundClient) {
val state get() = client.gl
private val foreground = HashMap<GLTexture2D, Config>() private val foreground = HashMap<GLTexture2D, Config>()
private val background = HashMap<GLTexture2D, Config>() private val background = HashMap<GLTexture2D, Config>()
private val matCache = HashMap<String, TileRenderer>() private val matCache = HashMap<String, TileRenderer>()
@ -44,14 +42,14 @@ class TileRenderers(val client: StarboundClient) {
} }
} }
private inner class Config(private val texture: GLTexture2D, private val color: RGBAColor) : RenderConfig<GLTileProgram>(state.programs.tile) { private inner class Config(private val texture: GLTexture2D, private val color: RGBAColor) : RenderConfig<GLTileProgram>(client.programs.tile) {
override val initialBuilderCapacity: Int override val initialBuilderCapacity: Int
get() = 1024 get() = 1024
override fun setup(transform: Matrix4f) { override fun setup(transform: Matrix4f) {
super.setup(transform) super.setup(transform)
state.activeTexture = 0 client.activeTexture = 0
state.depthTest = false client.depthTest = false
program.texture = 0 program.texture = 0
texture.bind() texture.bind()
texture.textureMagFilter = GL_NEAREST texture.textureMagFilter = GL_NEAREST
@ -97,7 +95,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
HALT HALT
} }
val state get() = renderers.state val state get() = renderers.client
val texture = def.renderParameters.texture?.imagePath?.value?.let { state.loadTexture(it).also { it.textureMagFilter = GL_NEAREST }} val texture = def.renderParameters.texture?.imagePath?.value?.let { state.loadTexture(it).also { it.textureMagFilter = GL_NEAREST }}
val equalityTester: EqualityRuleTester = when (def) { val equalityTester: EqualityRuleTester = when (def) {

View File

@ -3,7 +3,6 @@ package ru.dbotthepony.kstarbound.client.render.entity
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import ru.dbotthepony.kstarbound.client.world.ClientChunk import ru.dbotthepony.kstarbound.client.world.ClientChunk
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kvector.arrays.Matrix4fStack import ru.dbotthepony.kvector.arrays.Matrix4fStack
import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2d
@ -14,19 +13,12 @@ import ru.dbotthepony.kvector.vector.Vector2d
* Считается, что процесс отрисовки ограничен лишь одним слоем (т.е. отрисовка происходит в один проход) * Считается, что процесс отрисовки ограничен лишь одним слоем (т.е. отрисовка происходит в один проход)
*/ */
open class EntityRenderer(val client: StarboundClient, val entity: Entity, open var chunk: ClientChunk?) { open class EntityRenderer(val client: StarboundClient, val entity: Entity, open var chunk: ClientChunk?) {
inline val state: GLStateTracker get() = client.gl
open val renderPos: Vector2d get() = entity.position open val renderPos: Vector2d get() = entity.position
open fun render(stack: Matrix4fStack) { open fun render(stack: Matrix4fStack) {
} }
open fun renderDebug() {
if (chunk?.world?.client?.settings?.debugCollisions == true) {
state.quadWireframe(entity.movement.worldAABB)
}
}
open val layer: Int get() = Z_LEVEL_ENTITIES open val layer: Int get() = Z_LEVEL_ENTITIES
companion object { companion object {

View File

@ -9,9 +9,9 @@ class ItemRenderer(client: StarboundClient, entity: ItemEntity, chunk: ClientChu
private val def = entity.def private val def = entity.def
override fun render(stack: Matrix4fStack) { override fun render(stack: Matrix4fStack) {
state.programs.textured.use() client.programs.textured.use()
state.programs.textured.transform = stack.last() client.programs.textured.transform = stack.last()
state.activeTexture = 0 client.activeTexture = 0
state.programs.textured.texture = 0 client.programs.textured.texture = 0
} }
} }

View File

@ -1,14 +1,11 @@
package ru.dbotthepony.kstarbound.client.world package ru.dbotthepony.kstarbound.client.world
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.render.entity.EntityRenderer import ru.dbotthepony.kstarbound.client.render.entity.EntityRenderer
import ru.dbotthepony.kstarbound.world.Chunk import ru.dbotthepony.kstarbound.world.Chunk
import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.ChunkPos
import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kstarbound.world.entities.Entity
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos){ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos){
val state: GLStateTracker get() = world.client.gl
override fun foregroundChanges(cell: Cell) { override fun foregroundChanges(cell: Cell) {
super.foregroundChanges(cell) super.foregroundChanges(cell)

View File

@ -36,7 +36,7 @@ class ClientWorld(
loopY: Boolean = false loopY: Boolean = false
) : World<ClientWorld, ClientChunk>(seed, size, loopX, loopY) { ) : World<ClientWorld, ClientChunk>(seed, size, loopX, loopY) {
init { init {
physics.debugDraw = client.gl.box2dRenderer physics.debugDraw = client.box2dRenderer
} }
private fun determineChunkSize(cells: Int): Int { private fun determineChunkSize(cells: Int): Int {
@ -72,7 +72,6 @@ class ClientWorld(
inner class RenderRegion(val x: Int, val y: Int) { inner class RenderRegion(val x: Int, val y: Int) {
inner class Layer(private val view: ITileAccess, private val isBackground: Boolean) { inner class Layer(private val view: ITileAccess, private val isBackground: Boolean) {
private val state get() = client.gl
val bakedMeshes = ArrayList<Pair<ConfiguredMesh<*>, Long>>() val bakedMeshes = ArrayList<Pair<ConfiguredMesh<*>, Long>>()
var isDirty = true var isDirty = true
@ -104,7 +103,7 @@ class ClientWorld(
} }
for ((baked, builder, zLevel) in meshes.meshes()) { for ((baked, builder, zLevel) in meshes.meshes()) {
bakedMeshes.add(ConfiguredMesh(baked, Mesh(state, builder)) to zLevel) bakedMeshes.add(ConfiguredMesh(baked, Mesh(builder)) to zLevel)
} }
} }
} }
@ -134,7 +133,7 @@ class ClientWorld(
} }
for (type in liquidTypes) { for (type in liquidTypes) {
val builder = client.gl.programs.liquid.builder.builder val builder = client.programs.liquid.builder.builder
builder.begin() builder.begin()
@ -148,7 +147,7 @@ class ClientWorld(
} }
} }
liquidMesh.add(Mesh(client.gl, builder) to type.color) liquidMesh.add(Mesh(builder) to type.color)
} }
} }
@ -172,7 +171,7 @@ class ClientWorld(
layers.add(RenderLayer.Liquid.index) { layers.add(RenderLayer.Liquid.index) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
val program = client.gl.programs.liquid val program = client.programs.liquid
program.use() program.use()
program.transform = it.last() program.transform = it.last()
@ -268,7 +267,7 @@ class ClientWorld(
if (obj.pos.x in client.viewportCellX .. client.viewportCellX + client.viewportCellWidth && obj.pos.y in client.viewportCellY .. client.viewportCellY + client.viewportCellHeight) { if (obj.pos.x in client.viewportCellX .. client.viewportCellX + client.viewportCellWidth && obj.pos.y in client.viewportCellY .. client.viewportCellY + client.viewportCellHeight) {
//layers.add(RenderLayer.Object.index) { //layers.add(RenderLayer.Object.index) {
layers.add(obj.orientation?.renderLayer ?: continue) { m -> layers.add(obj.orientation?.renderLayer ?: continue) { m ->
client.gl.quadWireframe { client.quadWireframe {
it.quad( it.quad(
obj.pos.x.toFloat(), obj.pos.x.toFloat(),
obj.pos.y.toFloat(), obj.pos.y.toFloat(),
@ -281,7 +280,7 @@ class ClientWorld(
val (x, y) = obj.imagePosition val (x, y) = obj.imagePosition
it.render( it.render(
client.gl, client,
x = obj.pos.x.toFloat() + x / PIXELS_IN_STARBOUND_UNITf, x = obj.pos.x.toFloat() + x / PIXELS_IN_STARBOUND_UNITf,
y = obj.pos.y.toFloat() + y / PIXELS_IN_STARBOUND_UNITf y = obj.pos.y.toFloat() + y / PIXELS_IN_STARBOUND_UNITf
) )

View File

@ -9,14 +9,13 @@ import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.defs.image.SpriteReference
import ru.dbotthepony.kstarbound.io.json.consumeNull import ru.dbotthepony.kstarbound.io.json.consumeNull
import ru.dbotthepony.kstarbound.math.LineF import ru.dbotthepony.kstarbound.math.LineF
import ru.dbotthepony.kstarbound.util.contains import ru.dbotthepony.kstarbound.util.contains
import ru.dbotthepony.kvector.arrays.Matrix3f import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.arrays.Matrix4f
import ru.dbotthepony.kvector.arrays.Matrix4fStack import ru.dbotthepony.kvector.arrays.Matrix4fStack
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2f import ru.dbotthepony.kvector.vector.Vector2f
@ -30,7 +29,7 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
color: RGBAColor = RGBAColor.WHITE, color: RGBAColor = RGBAColor.WHITE,
fullbright: Boolean = false fullbright: Boolean = false
) : Drawable(position, color, fullbright) { ) : Drawable(position, color, fullbright) {
override fun render(gl: GLStateTracker, stack: Matrix4fStack, x: Float, y: Float) { override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
} }
@ -41,7 +40,7 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
color: RGBAColor = RGBAColor.WHITE, color: RGBAColor = RGBAColor.WHITE,
fullbright: Boolean = false fullbright: Boolean = false
) : Drawable(position, color, fullbright) { ) : Drawable(position, color, fullbright) {
override fun render(gl: GLStateTracker, stack: Matrix4fStack, x: Float, y: Float) { override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
} }
@ -64,16 +63,16 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
return Image(newPath, transform, centered, position, color, fullbright) return Image(newPath, transform, centered, position, color, fullbright)
} }
override fun render(gl: GLStateTracker, stack: Matrix4fStack, x: Float, y: Float) { override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) {
val sprite = path.sprite ?: return val sprite = path.sprite ?: return
val texture = gl.loadTexture(path.imagePath.value!!) val texture = client.loadTexture(path.imagePath.value!!)
if (centered) { if (centered) {
gl.quadTexture(texture) { client.quadTexture(texture) {
it.quad(x - (sprite.width / PIXELS_IN_STARBOUND_UNITf) * 0.5f, y - (sprite.height / PIXELS_IN_STARBOUND_UNITf) * 0.5f, x + sprite.width / PIXELS_IN_STARBOUND_UNITf * 0.5f, y + sprite.height / PIXELS_IN_STARBOUND_UNITf * 0.5f, QuadTransformers.uv(sprite)) it.quad(x - (sprite.width / PIXELS_IN_STARBOUND_UNITf) * 0.5f, y - (sprite.height / PIXELS_IN_STARBOUND_UNITf) * 0.5f, x + sprite.width / PIXELS_IN_STARBOUND_UNITf * 0.5f, y + sprite.height / PIXELS_IN_STARBOUND_UNITf * 0.5f, QuadTransformers.uv(sprite))
} }
} else { } else {
gl.quadTexture(texture) { client.quadTexture(texture) {
it.quad(x, y, x + sprite.width / PIXELS_IN_STARBOUND_UNITf, y + sprite.height / PIXELS_IN_STARBOUND_UNITf, QuadTransformers.uv(sprite)) it.quad(x, y, x + sprite.width / PIXELS_IN_STARBOUND_UNITf, y + sprite.height / PIXELS_IN_STARBOUND_UNITf, QuadTransformers.uv(sprite))
} }
} }
@ -81,14 +80,14 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
} }
class Empty(position: Vector2f = Vector2f.ZERO, color: RGBAColor = RGBAColor.WHITE, fullbright: Boolean = false) : Drawable(position, color, fullbright) { class Empty(position: Vector2f = Vector2f.ZERO, color: RGBAColor = RGBAColor.WHITE, fullbright: Boolean = false) : Drawable(position, color, fullbright) {
override fun render(gl: GLStateTracker, stack: Matrix4fStack, x: Float, y: Float) {} override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) {}
} }
open fun with(values: (String) -> String?): Drawable { open fun with(values: (String) -> String?): Drawable {
return this return this
} }
abstract fun render(gl: GLStateTracker = GLStateTracker.current(), stack: Matrix4fStack = gl.matrixStack, x: Float = 0f, y: Float = 0f) abstract fun render(client: StarboundClient = StarboundClient.current(), stack: Matrix4fStack = client.matrixStack, x: Float = 0f, y: Float = 0f)
companion object { companion object {
val EMPTY = Empty() val EMPTY = Empty()

View File

@ -18,7 +18,6 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement
import ru.dbotthepony.kstarbound.util.INotNullDelegate
import kotlin.properties.Delegates import kotlin.properties.Delegates
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KMutableProperty1
@ -139,16 +138,10 @@ class BuilderAdapter<T : Any> private constructor(
continue continue
} }
val delegate = property.property.getDelegate(instance) try {
property.property.get(instance)
if (delegate is INotNullDelegate && !delegate.isPresent) { } catch (err: Throwable) {
throw JsonSyntaxException("${property.name} in ${instance::class.qualifiedName} can not be null, but it is missing from JSON structure") throw JsonSyntaxException("${property.name} in ${instance::class.qualifiedName} does not like it being missing from JSON structure", err)
} else {
try {
property.property.get(instance)
} catch (err: Throwable) {
throw JsonSyntaxException("${property.name} in ${instance::class.qualifiedName} does not like it being missing from JSON structure", err)
}
} }
} }
} }

View File

@ -1,25 +0,0 @@
package ru.dbotthepony.kstarbound.util
class DoubleEdgeProgression : Iterator<Int> {
var value = 0
override fun hasNext(): Boolean {
return true
}
override fun next(): Int {
return nextInt()
}
fun nextInt(): Int {
return if (value > 0) {
val ret = value
value = -value
ret
} else {
val ret = value
value = -value + 1
ret
}
}
}

View File

@ -1,5 +0,0 @@
package ru.dbotthepony.kstarbound.util
interface INotNullDelegate {
val isPresent: Boolean
}

View File

@ -1,48 +0,0 @@
package ru.dbotthepony.kstarbound.util
import java.util.Spliterator
import java.util.function.Consumer
class IndexedArraySpliterator<T>(private val source: Array<T>, private val offset: Int = 0, private val size: Int = source.size) : Spliterator<IndexedArraySpliterator<T>.Entry> {
inner class Entry(val index: Int, val value: T)
init {
require(offset + size <= source.size) { "Invalid dimensions for spliterator: offset = $offset, size = $size, source has size of ${source.size}" }
}
private var index = 0
override fun tryAdvance(action: Consumer<in IndexedArraySpliterator<T>.Entry>): Boolean {
if (index < size) {
val pointer = offset + index++
action.accept(Entry(pointer, source[pointer]))
return true
}
return false
}
override fun trySplit(): Spliterator<IndexedArraySpliterator<T>.Entry>? {
if (index + 1 >= size) {
return null
}
val splitSize = size / 2
if (splitSize == 0) {
return null
}
val pointer = index + offset
index += splitSize
return IndexedArraySpliterator(source, pointer, splitSize)
}
override fun estimateSize(): Long {
return (size - index).toLong()
}
override fun characteristics(): Int {
return Spliterator.ORDERED or Spliterator.SIZED
}
}

View File

@ -1,69 +0,0 @@
package ru.dbotthepony.kstarbound.util
import it.unimi.dsi.fastutil.objects.ObjectSpliterators
import java.util.*
import java.util.stream.Stream
import java.util.stream.StreamSupport
import kotlin.reflect.KClass
class NotNullTwoDimensionalArray<T : Any>(clazz: KClass<T>, private val width: Int, private val height: Int, initializer: (Int, Int) -> T) {
data class Entry<out T>(
val x: Int,
val y: Int,
val value: T,
)
private val memory: Array<T> = java.lang.reflect.Array.newInstance(clazz.java, width * height) as Array<T>
init {
for (x in 0 until width) {
for (y in 0 until height) {
memory[x + y * width] = initializer.invoke(x, y)
}
}
}
fun isOutside(x: Int, y: Int): Boolean {
return (x !in 0 until width) || (y !in 0 until height)
}
operator fun get(x: Int, y: Int): T {
if (x !in 0 until width) {
throw IndexOutOfBoundsException("X $x is out of bounds between 0 and $width")
}
if (y !in 0 until height) {
throw IndexOutOfBoundsException("Y $y is out of bounds between 0 and $height")
}
return memory[x + y * width]
}
operator fun set(x: Int, y: Int, value: T): T {
if (x !in 0 until width) {
throw IndexOutOfBoundsException("X $x is out of bounds between 0 and $width")
}
if (y !in 0 until height) {
throw IndexOutOfBoundsException("Y $y is out of bounds between 0 and $height")
}
val old = memory[x + y * width]
memory[x + y * width] = value
return old
}
fun stream(): Stream<out T> {
return Arrays.stream(memory)
}
fun indexedStream(): Stream<out Entry<T>> {
return StreamSupport.stream(IndexedArraySpliterator(memory), false).map {
val x = it.index % width
val y = (it.index - x) / width
Entry(x, y, it.value)
}
}
}
inline fun <reified T : Any> NotNullTwoDimensionalArray(width: Int, height: Int, noinline initializer: (Int, Int) -> T) = NotNullTwoDimensionalArray(T::class, width, height, initializer)

View File

@ -1,26 +0,0 @@
package ru.dbotthepony.kstarbound.util
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlin.properties.Delegates
/**
* Аналог [Delegates.notNull], но со свойством [isInitialized]
*/
class NotNullVar<V : Any> : ReadWriteProperty<Any?, V>, INotNullDelegate {
private var value: V? = null
/**
* Имеет ли данный делегат не-null значение
*/
override val isPresent: Boolean
get() = value != null
override fun getValue(thisRef: Any?, property: KProperty<*>): V {
return value ?: throw IllegalStateException("Property ${property.name} was not initialized")
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) {
this.value = value
}
}

View File

@ -1,61 +0,0 @@
package ru.dbotthepony.kstarbound.util
import it.unimi.dsi.fastutil.objects.ObjectSpliterators
import java.util.Arrays
import java.util.stream.Stream
import java.util.stream.StreamSupport
import kotlin.reflect.KClass
class TwoDimensionalArray<T : Any>(clazz: KClass<T>, private val width: Int, private val height: Int) {
data class Entry<out T>(
val x: Int,
val y: Int,
val value: T,
)
private val memory: Array<T?> = java.lang.reflect.Array.newInstance(clazz.java, width * height) as Array<T?>
fun isOutside(x: Int, y: Int): Boolean {
return (x !in 0 until width) || (y !in 0 until height)
}
operator fun get(x: Int, y: Int): T? {
if (x !in 0 until width) {
throw IndexOutOfBoundsException("X $x is out of bounds between 0 and $width")
}
if (y !in 0 until height) {
throw IndexOutOfBoundsException("Y $y is out of bounds between 0 and $height")
}
return memory[x + y * width]
}
operator fun set(x: Int, y: Int, value: T?): T? {
if (x !in 0 until width) {
throw IndexOutOfBoundsException("X $x is out of bounds between 0 and $width")
}
if (y !in 0 until height) {
throw IndexOutOfBoundsException("Y $y is out of bounds between 0 and $height")
}
val old = memory[x + y * width]
memory[x + y * width] = value
return old
}
fun stream(): Stream<out T?> {
return Arrays.stream(memory)
}
fun indexedStream(): Stream<out Entry<T?>> {
return StreamSupport.stream(IndexedArraySpliterator(memory), false).map {
val x = it.index % width
val y = (it.index - x) / width
Entry(x, y, it.value)
}
}
}
inline fun <reified T : Any> TwoDimensionalArray(width: Int, height: Int) = TwoDimensionalArray(T::class, width, height)

View File

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

View File

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

View File

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

View File

@ -1,12 +0,0 @@
#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;
}