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("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")
}

View File

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

View File

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

View File

@ -1,34 +1,62 @@
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.lwjgl.BufferUtils
import org.lwjgl.glfw.Callbacks
import org.lwjgl.glfw.GLFW
import org.lwjgl.glfw.GLFWErrorCallback
import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL46.*
import org.lwjgl.opengl.GLCapabilities
import org.lwjgl.system.MemoryStack
import org.lwjgl.system.MemoryUtil
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
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.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.StreamVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
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.Font
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
import ru.dbotthepony.kstarbound.client.render.TextAlignY
import ru.dbotthepony.kstarbound.client.render.TileRenderers
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.roundTowardsPositiveInfinity
import ru.dbotthepony.kstarbound.util.JVMTimeSource
import ru.dbotthepony.kstarbound.util.PausableTimeSource
import ru.dbotthepony.kstarbound.util.formatBytesShort
import ru.dbotthepony.kstarbound.world.LightCalculator
import ru.dbotthepony.kstarbound.world.api.ICellAccess
import ru.dbotthepony.kstarbound.world.api.IChunkCell
import ru.dbotthepony.kvector.api.IStruct4f
import ru.dbotthepony.kvector.arrays.Matrix4f
import ru.dbotthepony.kvector.arrays.Matrix4fStack
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.RGBAColor
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.Vector3f
import java.io.Closeable
import java.io.File
import java.lang.ref.Cleaner
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.time.Duration
import java.util.*
import java.util.concurrent.locks.LockSupport
import java.util.concurrent.locks.ReentrantLock
import kotlin.collections.ArrayList
import kotlin.math.roundToInt
class StarboundClient : Closeable {
val time = PausableTimeSource(JVMTimeSource.INSTANCE)
val window: Long
val camera = Camera(this)
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
private set
@ -65,9 +121,519 @@ class StarboundClient : Closeable {
private set
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 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) {
if (replace) {
if (startupTextList.isEmpty()) {
@ -82,6 +648,12 @@ class StarboundClient : Closeable {
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 {
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)
}
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)
var world: ClientWorld? = ClientWorld(this, 0L, Vector2i(3000, 2000), true)
init {
putDebugLog("Initialized OpenGL context")
gl.clearColor = RGBAColor.SLATE_GRAY
clearColor = RGBAColor.SLATE_GRAY
gl.blend = true
gl.blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
blend = true
blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
}
var frameRenderTime = 0.0
private set
private var lastRender = JVMTimeSource.INSTANCE.seconds
val framesPerSecond get() = if (frameRenderTime == 0.0) 1.0 else 1.0 / frameRenderTime
private val frameRenderTimes = DoubleArray(60) { 1.0 }
@ -267,17 +764,6 @@ class StarboundClient : Closeable {
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 {
override fun getCell(x: Int, y: Int): IChunkCell? {
return world?.getCell(x + viewportCellX, y + viewportCellY)
@ -291,11 +777,9 @@ class StarboundClient : Closeable {
var viewportLighting = LightCalculator(viewportCells, viewportCellWidth, viewportCellHeight)
private set
val viewportLightingTexture = gl.newTexture("Viewport Lighting")
val viewportLightingTexture = newTexture("Viewport Lighting")
private var viewportLightingMem: ByteBuffer? = null
var fullbright = true
fun updateViewportParams() {
viewportRectangle = AABB.rectangle(
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) {
onViewportChanged.add(callback)
}
@ -345,10 +823,8 @@ class StarboundClient : Closeable {
onPostDrawWorldOnce.add(lambda)
}
private var lastRender = JVMTimeSource.INSTANCE.seconds
fun renderFrame(): Boolean {
gl.ensureSameThread()
ensureSameThread()
val diff = JVMTimeSource.INSTANCE.seconds - lastRender
@ -367,7 +843,7 @@ class StarboundClient : Closeable {
val world = world
if (!isRenderingGame) {
gl.cleanup()
cleanup()
GLFW.glfwPollEvents()
if (world != null) {
@ -385,11 +861,11 @@ class StarboundClient : Closeable {
if (Starbound.initialized)
world.think()
gl.clearColor = RGBAColor.SLATE_GRAY
clearColor = RGBAColor.SLATE_GRAY
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) // центр экрана + координаты отрисовки мира
.scale(x = settings.zoom * PIXELS_IN_STARBOUND_UNITf, y = settings.zoom * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера
.translateWithMultiplication(-camera.pos.x, -camera.pos.y) // перемещаем вид к камере
@ -409,7 +885,7 @@ class StarboundClient : Closeable {
layers = layers,
size = viewportRectangle)
layers.render(gl.matrixStack)
layers.render(matrixStack)
val viewportLightingMem = viewportLightingMem
@ -420,8 +896,8 @@ class StarboundClient : Closeable {
viewportLighting.calculate(viewportLightingMem, viewportLighting.width.coerceAtMost(4096), viewportLighting.height.coerceAtMost(4096))
viewportLightingMem.position(0)
val old = gl.textureUnpackAlignment
gl.textureUnpackAlignment = if (viewportLighting.width.coerceAtMost(4096) % 4 == 0) 4 else 1
val old = textureUnpackAlignment
textureUnpackAlignment = if (viewportLighting.width.coerceAtMost(4096) % 4 == 0) 4 else 1
viewportLightingTexture.upload(
GL_RGB,
@ -432,16 +908,16 @@ class StarboundClient : Closeable {
viewportLightingMem
)
gl.textureUnpackAlignment = old
textureUnpackAlignment = old
viewportLightingTexture.textureMinFilter = GL_LINEAR
//viewportLightingTexture.textureMagFilter = GL_NEAREST
//viewportLightingTexture.generateMips()
gl.blendFunc = BlendFunc.MULTIPLY_BY_SRC
blendFunc = BlendFunc.MULTIPLY_BY_SRC
gl.quadTexture(viewportLightingTexture) {
quadTexture(viewportLightingTexture) {
it.quad(
(viewportCellX).toFloat(),
(viewportCellY).toFloat(),
@ -451,7 +927,7 @@ class StarboundClient : Closeable {
)
}
gl.blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
}
world.physics.debugDraw()
@ -460,10 +936,10 @@ class StarboundClient : Closeable {
lambda.invoke()
}
gl.matrixStack.pop()
matrixStack.pop()
}
gl.matrixStack.clear(viewportMatrixScreen)
matrixStack.clear(viewportMatrixScreen)
val thisTime = System.currentTimeMillis()
@ -474,20 +950,20 @@ class StarboundClient : Closeable {
alpha = (finishStartupRendering - thisTime) / 1000f
}
gl.matrixStack.push()
gl.matrixStack.last().translateWithMultiplication(y = viewportHeight.toFloat())
matrixStack.push()
matrixStack.last().translateWithMultiplication(y = viewportHeight.toFloat())
var shade = 255
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))
gl.matrixStack.last().translateWithMultiplication(y = -size.height * 1.2f)
val size = font.render(startupTextList[i], alignY = TextAlignY.BOTTOM, scale = 0.4f, color = RGBAColor(shade / 255f, shade / 255f, shade / 255f, alpha))
matrixStack.last().translateWithMultiplication(y = -size.height * 1.2f)
if (shade > 120) {
shade -= 10
}
}
gl.matrixStack.pop()
matrixStack.pop()
}
for (fn in onDrawGUI) {
@ -496,8 +972,8 @@ class StarboundClient : Closeable {
val runtime = Runtime.getRuntime()
gl.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("FPS: ${(averageFramesPerSecond * 100f).toInt() / 100f}", scale = 0.4f)
font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = font.lineHeight * 0.5f, scale = 0.4f)
GLFW.glfwSwapBuffers(window)
GLFW.glfwPollEvents()
@ -505,13 +981,11 @@ class StarboundClient : Closeable {
camera.think(Starbound.TICK_TIME_ADVANCE)
gl.cleanup()
cleanup()
return true
}
private val terminateCallbacks = ArrayList<() -> Unit>()
fun onTermination(lambda: () -> Unit) {
terminateCallbacks.add(lambda)
}
@ -537,5 +1011,25 @@ class StarboundClient : Closeable {
companion object {
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_LINEAR
import org.lwjgl.opengl.GL30.GL_RGB
import org.lwjgl.opengl.GL30.GL_TEXTURE
import org.lwjgl.opengl.GL30.GL_TEXTURE_2D
import org.lwjgl.opengl.GL30.glCheckFramebufferStatus
import org.lwjgl.opengl.GL30.glFramebufferTexture2D
import org.lwjgl.opengl.GL45.glCheckNamedFramebufferStatus
import org.lwjgl.opengl.GL45.glNamedFramebufferTexture
import org.lwjgl.opengl.GL46
import ru.dbotthepony.kstarbound.client.StarboundClient
class GLFrameBuffer(val state: GLStateTracker) {
init {
state.ensureSameThread()
}
class GLFrameBuffer {
val client = StarboundClient.current()
val pointer = GL46.glGenFramebuffers()
init {
checkForGLError("Creating framebuffer")
state.registerCleanable(this, GL46::glDeleteFramebuffers, pointer)
client.registerCleanable(this, GL46::glDeleteFramebuffers, pointer)
}
val isComplete: Boolean get() {
state.ensureSameThread()
client.ensureSameThread()
return glCheckNamedFramebufferStatus(pointer, GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE
}
@ -38,16 +33,16 @@ class GLFrameBuffer(val state: GLStateTracker) {
throw IllegalStateException("Already has texture attached")
}
val texture = GLTexture2D(state, "framebuffer_$pointer")
val texture = GLTexture2D("framebuffer_$pointer")
texture.allocate(format, width, height)
val old = state.framebuffer
val old = client.framebuffer
bind()
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.pointer, 0)
checkForGLError()
this.texture = texture
texture.textureMinFilter = GL_LINEAR
state.framebuffer = old
client.framebuffer = old
}
fun reattachTexture(width: Int, height: Int, format: Int = GL_RGB) {
@ -56,24 +51,24 @@ class GLFrameBuffer(val state: GLStateTracker) {
}
fun bind() {
state.framebuffer = this
client.framebuffer = this
}
fun bindWrite() {
state.writeFramebuffer = this
client.writeFramebuffer = this
}
fun bindRead() {
state.readFramebuffer = this
client.readFramebuffer = this
}
fun unbind() {
if (state.writeFramebuffer == this) {
state.writeFramebuffer = null
if (client.writeFramebuffer == this) {
client.writeFramebuffer = null
}
if (state.readFramebuffer == this) {
state.readFramebuffer = null
if (client.readFramebuffer == this) {
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.lwjgl.opengl.GL46.*
import org.lwjgl.stb.STBImage
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kvector.vector.Vector2i
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) {
thisRef.state.ensureSameThread()
thisRef.client.ensureSameThread()
if (this.value == value) return
this.value = value
glTextureParameteri(thisRef.pointer, flag, value)
@ -29,16 +30,13 @@ private class GLTexturePropertyTracker(private val flag: Int, private var value:
}
@Suppress("SameParameterValue")
class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") {
init {
state.ensureSameThread()
}
class GLTexture2D(val name: String = "<unknown>") {
val client = StarboundClient.current()
val pointer = glGenTextures()
init {
checkForGLError()
state.registerCleanable(this, ::glDeleteTextures, pointer)
client.registerCleanable(this, ::glDeleteTextures, pointer)
}
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)
fun bind(): GLTexture2D {
state.texture2D = this
client.texture2D = this
return this
}
fun generateMips(): GLTexture2D {
state.ensureSameThread()
client.ensureSameThread()
glGenerateTextureMipmap(pointer)
checkForGLError("Generating texture mipmaps")
return this
@ -150,7 +148,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") {
}
fun upload(path: File, memoryFormat: Int, bufferFormat: Int): GLTexture2D {
state.ensureSameThread()
client.ensureSameThread()
if (!path.exists()) {
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 {
state.ensureSameThread()
client.ensureSameThread()
if (!path.exists()) {
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 {
state.ensureSameThread()
client.ensureSameThread()
val getwidth = intArrayOf(0)
val getheight = intArrayOf(0)
@ -228,7 +226,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") {
}
fun upload(buff: ByteBuffer): GLTexture2D {
state.ensureSameThread()
client.ensureSameThread()
val getwidth = intArrayOf(0)
val getheight = intArrayOf(0)
@ -254,7 +252,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") {
}
fun upload(data: Image): GLTexture2D {
state.ensureSameThread()
client.ensureSameThread()
val bufferFormat = when (val numChannels = data.amountOfChannels) {
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,
)
}
}
}

View File

@ -1,32 +1,34 @@
package ru.dbotthepony.kstarbound.client.gl
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()
init {
checkForGLError()
state.registerCleanable(this, ::glDeleteVertexArrays, pointer)
client.registerCleanable(this, ::glDeleteVertexArrays, pointer)
}
fun bind(): VertexArrayObject {
return state.bind(this)
return client.bind(this)
}
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 {
state.ensureSameThread()
client.ensureSameThread()
glVertexAttribPointer(position, size, type, normalize, stride, offset)
checkForGLError()
return this
}
fun enableAttribute(position: Int): VertexArrayObject {
state.ensureSameThread()
client.ensureSameThread()
glEnableVertexArrayAttrib(pointer, position)
//glEnableVertexAttribArray(position)
checkForGLError()

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.client.gl
import org.lwjgl.opengl.GL46.*
import org.lwjgl.system.MemoryUtil
import ru.dbotthepony.kstarbound.client.StarboundClient
import java.nio.ByteBuffer
enum class VBOType(val glType: Int) {
@ -9,36 +10,37 @@ enum class VBOType(val glType: Int) {
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()
init {
checkForGLError("Creating Vertex Buffer Object")
state.registerCleanable(this, ::glDeleteBuffers, pointer)
client.registerCleanable(this, ::glDeleteBuffers, pointer)
}
val isArray get() = type == VBOType.ARRAY
val isElementArray get() = type == VBOType.ELEMENT_ARRAY
fun bind(): VertexBufferObject {
state.bind(this)
client.bind(this)
return this
}
fun unbind(): VertexBufferObject {
state.unbind(this)
client.unbind(this)
return this
}
fun bufferData(data: ByteBuffer, usage: Int): VertexBufferObject {
state.ensureSameThread()
client.ensureSameThread()
glNamedBufferData(pointer, data, usage)
checkForGLError()
return this
}
fun bufferData(data: ByteBuffer, usage: Int, length: Long): VertexBufferObject {
state.ensureSameThread()
client.ensureSameThread()
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()}!")
@ -51,30 +53,35 @@ class VertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOType.
}
fun bufferData(data: IntArray, usage: Int): VertexBufferObject {
state.ensureSameThread()
client.ensureSameThread()
glNamedBufferData(pointer, data, usage)
checkForGLError()
return this
}
fun bufferData(data: FloatArray, usage: Int): VertexBufferObject {
state.ensureSameThread()
client.ensureSameThread()
glNamedBufferData(pointer, data, usage)
checkForGLError()
return this
}
fun bufferData(data: DoubleArray, usage: Int): VertexBufferObject {
state.ensureSameThread()
client.ensureSameThread()
glNamedBufferData(pointer, data, usage)
checkForGLError()
return this
}
fun bufferData(data: LongArray, usage: Int): VertexBufferObject {
state.ensureSameThread()
client.ensureSameThread()
glNamedBufferData(pointer, data, usage)
checkForGLError()
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.Object2ObjectFunction
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.vertex.GLAttributeList
import ru.dbotthepony.kvector.api.IStruct2f
@ -24,19 +24,16 @@ import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
open class GLShaderProgram(
val state: GLStateTracker,
shaders: Iterable<GLStateTracker.Shader>,
shaders: Iterable<GLShader>,
val attributes: GLAttributeList
) {
init {
state.ensureSameThread()
}
val client = StarboundClient.current()
val pointer = glCreateProgram()
init {
checkForGLError("Creating shader program")
state.registerCleanable(this, ::glDeleteProgram, pointer)
client.registerCleanable(this, ::glDeleteProgram, pointer)
for (shader in shaders) {
glAttachShader(pointer, shader.pointer)
@ -56,7 +53,7 @@ open class GLShaderProgram(
}
fun use(): GLShaderProgram {
state.program = this
client.program = this
return this
}
@ -64,7 +61,7 @@ open class GLShaderProgram(
private val uniformsExist = Object2BooleanArrayMap<String>()
fun isUniformPresent(name: String): Boolean {
state.ensureSameThread()
client.ensureSameThread()
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> {
init {
state.ensureSameThread()
client.ensureSameThread()
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) {
override var value: Float = 0f
set(value) {
state.ensureSameThread()
client.ensureSameThread()
if (field != value) {
glProgramUniform1f(pointer, location, value)
@ -135,7 +132,7 @@ open class GLShaderProgram(
override var value: IStruct2f = Vector2f.ZERO
set(value) {
state.ensureSameThread()
client.ensureSameThread()
val (v0, v1) = value
@ -157,7 +154,7 @@ open class GLShaderProgram(
override var value: IStruct3f = Vector3f.ZERO
set(value) {
state.ensureSameThread()
client.ensureSameThread()
val (v0, v1, v2) = value
@ -179,7 +176,7 @@ open class GLShaderProgram(
inner class F3x3Uniform(name: String) : Uniform<Matrix3f>(name) {
override var value: Matrix3f = Matrix3f.zero()
set(value) {
state.ensureSameThread()
client.ensureSameThread()
if (field != value) {
buff3x3.position(0)
@ -198,7 +195,7 @@ open class GLShaderProgram(
override var value: Matrix4f = Matrix4f.zero()
set(value) {
state.ensureSameThread()
client.ensureSameThread()
buff4x4.position(0)
value.storeRowColumn(buff4x4)
@ -223,7 +220,7 @@ open class GLShaderProgram(
override var value: IStruct4f = Vector4f.ZERO
set(value) {
state.ensureSameThread()
client.ensureSameThread()
val (v0, v1, v2, v3) = value
@ -243,7 +240,7 @@ open class GLShaderProgram(
inner class IUniform(name: String) : Uniform<Int>(name) {
override var value: Int = 0
set(value) {
state.ensureSameThread()
client.ensureSameThread()
if (field != value) {
glProgramUniform1i(pointer, location, value)

View File

@ -1,33 +1,27 @@
package ru.dbotthepony.kstarbound.client.gl.shader
import ru.dbotthepony.kstarbound.client.gl.BlendFunc
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.GLType
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.QuadTransformers
import ru.dbotthepony.kvector.arrays.Matrix4f
import ru.dbotthepony.kvector.vector.RGBAColor
private fun GLStateTracker.shaders(name: String): List<GLStateTracker.Shader> {
return listOf(internalVertex("shaders/$name.vsh"), internalFragment("shaders/$name.fsh"))
private fun internalVertex(string: String) = StarboundClient.current().internalVertex(string)
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> {
return listOf(
internalVertex(name),
internalFragment(name),
internalGeometry(name)
)
}
class GLLiquidProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("liquid"), FORMAT) {
class GLLiquidProgram : GLShaderProgram(shaders("liquid"), FORMAT) {
var baselineColor by F4Uniform("baselineColor")
var transform by F4x4Uniform("transform")
val builder by lazy {
StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 16384)
StreamVertexBuilder(FORMAT, GeometryType.QUADS, 16384)
}
companion object {
@ -35,118 +29,11 @@ class GLLiquidProgram(state: GLStateTracker) : GLShaderProgram(state, state.shad
}
}
class GLLightProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("light"), FORMAT) {
var baselineColor by F4Uniform("baselineColor")
class GLFlatColorProgram : GLShaderProgram(shaders("flat_color"), FORMAT) {
var transform by F4x4Uniform("transform")
val builder by lazy {
StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 32)
}
companion object {
val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).push(GLType.VEC2F).build()
}
}
class 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)
StreamVertexBuilder(FORMAT, GeometryType.QUADS, 16384)
}
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 color by F4Uniform("color")
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 color by F4Uniform("color")
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 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 texture by IUniform("texture0")
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 {
@ -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 texture by IUniform("texture0")
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 {
@ -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 texture by IUniform("texture0")
var color by F4Uniform("color")
@ -226,18 +113,13 @@ class GLTexturedColoredProgram(state: GLStateTracker) : GLShaderProgram(state, l
}
}
class GLPrograms(val state: GLStateTracker) {
val tile by lazy { GLTileProgram(state) }
val font by lazy { GLFontProgram(state) }
val flat by lazy { GLFlatProgram(state) }
val flatColor by lazy { GLFlatColorProgram(state) }
val liquid by lazy { GLLiquidProgram(state) }
val light by lazy { GLLightProgram(state) }
val textured by lazy { GLTexturedProgram(state) }
val textured2d by lazy { GLTextured2dProgram(state) }
val texturedColored by lazy { GLTexturedColoredProgram(state) }
val viewColorQuad by lazy { GLColorQuadProgram(state) }
val viewTextureQuad by lazy { GLTextureQuadProgram(state) }
val viewTextureBlurQuad by lazy { GLTextureBlurredQuadProgram(state) }
class GLPrograms {
val tile = GLTileProgram()
val font = GLFontProgram()
val flat = GLFlatProgram()
val flatColor = GLFlatColorProgram()
val liquid = GLLiquidProgram()
val textured = GLTexturedProgram()
val textured2d = GLTextured2dProgram()
val texturedColored = GLTexturedColoredProgram()
}

View File

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

View File

@ -4,13 +4,14 @@ import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kbox2d.api.IDebugDraw
import ru.dbotthepony.kbox2d.api.Transform
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.Vector2d
import kotlin.math.cos
import kotlin.math.sin
class Box2DRenderer(val state: GLStateTracker) : IDebugDraw {
class Box2DRenderer : IDebugDraw {
val state = StarboundClient.current()
override var drawShapes: Boolean = false
override var drawJoints: 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_UP -> pressedUp = 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.objects.Object2ObjectFunction
import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.freetype.LoadFlag
import ru.dbotthepony.kstarbound.client.gl.*
import ru.dbotthepony.kstarbound.client.freetype.struct.FT_Pixel_Mode
@ -56,10 +57,10 @@ enum class TextAlignY {
}
class Font(
val state: GLStateTracker,
val font: String = "hobo.ttf",
val size: Int = 48
) {
val state = StarboundClient.current()
val face = state.freeType.Face(font, 0L)
init {

View File

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

View File

@ -4,10 +4,10 @@ import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram
import ru.dbotthepony.kvector.arrays.Matrix4f
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 fun setup(transform: Matrix4f = state.matrixStack.last()) {
open fun setup(transform: Matrix4f = client.matrixStack.last()) {
program.use()
}

View File

@ -20,11 +20,9 @@ import kotlin.collections.HashMap
/**
* Хранит в себе программы для отрисовки определённых [TileDefinition]
*
* Создаётся единожды как потомок [GLStateTracker]
* Создаётся единожды как потомок [Graphics]
*/
class TileRenderers(val client: StarboundClient) {
val state get() = client.gl
private val foreground = HashMap<GLTexture2D, Config>()
private val background = HashMap<GLTexture2D, Config>()
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
get() = 1024
override fun setup(transform: Matrix4f) {
super.setup(transform)
state.activeTexture = 0
state.depthTest = false
client.activeTexture = 0
client.depthTest = false
program.texture = 0
texture.bind()
texture.textureMagFilter = GL_NEAREST
@ -97,7 +95,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
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 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 ru.dbotthepony.kstarbound.client.world.ClientChunk
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kvector.arrays.Matrix4fStack
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?) {
inline val state: GLStateTracker get() = client.gl
open val renderPos: Vector2d get() = entity.position
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
companion object {

View File

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

View File

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

View File

@ -36,7 +36,7 @@ class ClientWorld(
loopY: Boolean = false
) : World<ClientWorld, ClientChunk>(seed, size, loopX, loopY) {
init {
physics.debugDraw = client.gl.box2dRenderer
physics.debugDraw = client.box2dRenderer
}
private fun determineChunkSize(cells: Int): Int {
@ -72,7 +72,6 @@ class ClientWorld(
inner class RenderRegion(val x: Int, val y: Int) {
inner class Layer(private val view: ITileAccess, private val isBackground: Boolean) {
private val state get() = client.gl
val bakedMeshes = ArrayList<Pair<ConfiguredMesh<*>, Long>>()
var isDirty = true
@ -104,7 +103,7 @@ class ClientWorld(
}
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) {
val builder = client.gl.programs.liquid.builder.builder
val builder = client.programs.liquid.builder.builder
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) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
val program = client.gl.programs.liquid
val program = client.programs.liquid
program.use()
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) {
//layers.add(RenderLayer.Object.index) {
layers.add(obj.orientation?.renderLayer ?: continue) { m ->
client.gl.quadWireframe {
client.quadWireframe {
it.quad(
obj.pos.x.toFloat(),
obj.pos.y.toFloat(),
@ -281,7 +280,7 @@ class ClientWorld(
val (x, y) = obj.imagePosition
it.render(
client.gl,
client,
x = obj.pos.x.toFloat() + x / 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 org.apache.logging.log4j.LogManager
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.defs.image.SpriteReference
import ru.dbotthepony.kstarbound.io.json.consumeNull
import ru.dbotthepony.kstarbound.math.LineF
import ru.dbotthepony.kstarbound.util.contains
import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.arrays.Matrix4f
import ru.dbotthepony.kvector.arrays.Matrix4fStack
import ru.dbotthepony.kvector.vector.RGBAColor
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,
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) {
TODO("Not yet implemented")
}
}
@ -41,7 +40,7 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
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) {
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)
}
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 texture = gl.loadTexture(path.imagePath.value!!)
val texture = client.loadTexture(path.imagePath.value!!)
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))
}
} 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))
}
}
@ -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) {
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 {
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 {
val EMPTY = Empty()

View File

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