Geometry batching
This commit is contained in:
parent
1479f7483a
commit
178953bce7
@ -83,7 +83,7 @@ dependencies {
|
|||||||
implementation("com.github.jnr:jnr-ffi:2.2.13")
|
implementation("com.github.jnr:jnr-ffi:2.2.13")
|
||||||
|
|
||||||
implementation("ru.dbotthepony:kbox2d:2.4.1.6")
|
implementation("ru.dbotthepony:kbox2d:2.4.1.6")
|
||||||
implementation("ru.dbotthepony:kvector:2.9.0")
|
implementation("ru.dbotthepony:kvector:2.9.2")
|
||||||
|
|
||||||
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
|
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import org.lwjgl.glfw.Callbacks
|
|||||||
import org.lwjgl.glfw.GLFW
|
import org.lwjgl.glfw.GLFW
|
||||||
import org.lwjgl.glfw.GLFWErrorCallback
|
import org.lwjgl.glfw.GLFWErrorCallback
|
||||||
import org.lwjgl.opengl.GL
|
import org.lwjgl.opengl.GL
|
||||||
import org.lwjgl.opengl.GL11
|
|
||||||
import org.lwjgl.opengl.GL45.*
|
import org.lwjgl.opengl.GL45.*
|
||||||
import org.lwjgl.opengl.GLCapabilities
|
import org.lwjgl.opengl.GLCapabilities
|
||||||
import org.lwjgl.system.MemoryStack
|
import org.lwjgl.system.MemoryStack
|
||||||
@ -30,6 +29,8 @@ import ru.dbotthepony.kstarbound.client.gl.properties.GLGenericTracker
|
|||||||
import ru.dbotthepony.kstarbound.client.gl.properties.GLObjectTracker
|
import ru.dbotthepony.kstarbound.client.gl.properties.GLObjectTracker
|
||||||
import ru.dbotthepony.kstarbound.client.gl.properties.GLStateIntTracker
|
import ru.dbotthepony.kstarbound.client.gl.properties.GLStateIntTracker
|
||||||
import ru.dbotthepony.kstarbound.client.gl.properties.GLStateSwitchTracker
|
import ru.dbotthepony.kstarbound.client.gl.properties.GLStateSwitchTracker
|
||||||
|
import ru.dbotthepony.kstarbound.client.gl.properties.GLTexturesTracker
|
||||||
|
import ru.dbotthepony.kstarbound.client.gl.shader.FontProgram
|
||||||
import ru.dbotthepony.kstarbound.client.gl.shader.GLPrograms
|
import ru.dbotthepony.kstarbound.client.gl.shader.GLPrograms
|
||||||
import ru.dbotthepony.kstarbound.client.gl.shader.GLShader
|
import ru.dbotthepony.kstarbound.client.gl.shader.GLShader
|
||||||
import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram
|
import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram
|
||||||
@ -72,6 +73,7 @@ import java.util.*
|
|||||||
import java.util.concurrent.locks.LockSupport
|
import java.util.concurrent.locks.LockSupport
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class StarboundClient : Closeable {
|
class StarboundClient : Closeable {
|
||||||
@ -260,15 +262,7 @@ class StarboundClient : Closeable {
|
|||||||
.weakValues()
|
.weakValues()
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private val missingTexture: GLTexture2D by lazy {
|
private val fontShaderPrograms = ArrayList<WeakReference<FontProgram>>()
|
||||||
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"
|
|
||||||
private val regularShaderPrograms = ArrayList<WeakReference<GLShaderProgram.Regular>>()
|
|
||||||
private val uberShaderPrograms = ArrayList<WeakReference<UberShader>>()
|
private val uberShaderPrograms = ArrayList<WeakReference<UberShader>>()
|
||||||
|
|
||||||
val lightMapLocation = maxTextureBlocks - 1
|
val lightMapLocation = maxTextureBlocks - 1
|
||||||
@ -278,8 +272,8 @@ class StarboundClient : Closeable {
|
|||||||
uberShaderPrograms.add(WeakReference(program))
|
uberShaderPrograms.add(WeakReference(program))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (program is GLShaderProgram.Regular) {
|
if (program is FontProgram) {
|
||||||
regularShaderPrograms.add(WeakReference(program))
|
fontShaderPrograms.add(WeakReference(program))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,23 +333,7 @@ class StarboundClient : Closeable {
|
|||||||
var framebuffer by GLObjectTracker<GLFrameBuffer>(::glBindFramebuffer, GL_FRAMEBUFFER)
|
var framebuffer by GLObjectTracker<GLFrameBuffer>(::glBindFramebuffer, GL_FRAMEBUFFER)
|
||||||
var program by GLObjectTracker<GLShaderProgram>(::glUseProgram)
|
var program by GLObjectTracker<GLShaderProgram>(::glUseProgram)
|
||||||
|
|
||||||
private val textures = Array(maxTextureBlocks) { GLObjectTracker<GLTexture2D>(GL11::glBindTexture, GL_TEXTURE_2D) }
|
val textures2D = GLTexturesTracker<GLTexture2D>(maxTextureBlocks)
|
||||||
|
|
||||||
var activeTexture = 0
|
|
||||||
set(value) {
|
|
||||||
ensureSameThread()
|
|
||||||
|
|
||||||
if (field != value) {
|
|
||||||
require(value in 0 until maxTextureBlocks) { "Texture block out of bounds: $value (max texture blocks is $maxTextureBlocks)" }
|
|
||||||
glActiveTexture(GL_TEXTURE0 + value)
|
|
||||||
checkForGLError()
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var texture2D: GLTexture2D?
|
|
||||||
get() = textures[activeTexture].get()
|
|
||||||
set(value) { textures[activeTexture].accept(value) }
|
|
||||||
|
|
||||||
var clearColor by GLGenericTracker<IStruct4f>(RGBAColor.WHITE) {
|
var clearColor by GLGenericTracker<IStruct4f>(RGBAColor.WHITE) {
|
||||||
val (r, g, b, a) = it
|
val (r, g, b, a) = it
|
||||||
@ -376,7 +354,8 @@ class StarboundClient : Closeable {
|
|||||||
checkForGLError()
|
checkForGLError()
|
||||||
}
|
}
|
||||||
|
|
||||||
val whiteTexture = GLTexture2D("white")
|
val whiteTexture = GLTexture2D(1, 1, GL_RGB8)
|
||||||
|
val missingTexture = GLTexture2D(8, 8, GL_RGB8)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val buffer = ByteBuffer.allocateDirect(3)
|
val buffer = ByteBuffer.allocateDirect(3)
|
||||||
@ -384,7 +363,42 @@ class StarboundClient : Closeable {
|
|||||||
buffer.put(0xFF.toByte())
|
buffer.put(0xFF.toByte())
|
||||||
buffer.put(0xFF.toByte())
|
buffer.put(0xFF.toByte())
|
||||||
buffer.position(0)
|
buffer.position(0)
|
||||||
whiteTexture.upload(GL_RGB, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, buffer)
|
whiteTexture.upload(GL_RGB, GL_UNSIGNED_BYTE, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val buffer = ByteBuffer.allocateDirect(3 * 8 * 8)
|
||||||
|
|
||||||
|
for (row in 0 until 4) {
|
||||||
|
for (x in 0 until 4) {
|
||||||
|
buffer.put(0x80.toByte())
|
||||||
|
buffer.put(0x0.toByte())
|
||||||
|
buffer.put(0x80.toByte())
|
||||||
|
}
|
||||||
|
|
||||||
|
for (x in 0 until 4) {
|
||||||
|
buffer.put(0x0.toByte())
|
||||||
|
buffer.put(0x0.toByte())
|
||||||
|
buffer.put(0x0.toByte())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (row in 0 until 4) {
|
||||||
|
for (x in 0 until 4) {
|
||||||
|
buffer.put(0x0.toByte())
|
||||||
|
buffer.put(0x0.toByte())
|
||||||
|
buffer.put(0x0.toByte())
|
||||||
|
}
|
||||||
|
|
||||||
|
for (x in 0 until 4) {
|
||||||
|
buffer.put(0x80.toByte())
|
||||||
|
buffer.put(0x0.toByte())
|
||||||
|
buffer.put(0x80.toByte())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.position()
|
||||||
|
missingTexture.upload(GL_RGB, GL_UNSIGNED_BYTE, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setViewport(x: Int, y: Int, width: Int, height: Int) {
|
fun setViewport(x: Int, y: Int, width: Int, height: Int) {
|
||||||
@ -454,7 +468,6 @@ class StarboundClient : Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isSameThread() = thread === Thread.currentThread()
|
fun isSameThread() = thread === Thread.currentThread()
|
||||||
fun newTexture(name: String = "<unknown>") = GLTexture2D(name)
|
|
||||||
|
|
||||||
fun loadTexture(path: String): GLTexture2D {
|
fun loadTexture(path: String): GLTexture2D {
|
||||||
ensureSameThread()
|
ensureSameThread()
|
||||||
@ -464,13 +477,27 @@ class StarboundClient : Closeable {
|
|||||||
val data = Image.get(it)
|
val data = Image.get(it)
|
||||||
|
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
LOGGER.error("Texture {} is missing! Falling back to {}", it, missingTexturePath)
|
LOGGER.error("Texture $it is not found, falling back to missing texture")
|
||||||
missingTexture
|
missingTexture
|
||||||
} else {
|
} else {
|
||||||
newTexture(it).upload(data).also {
|
val tex = GLTexture2D(data.width, data.height, when (data.amountOfChannels) {
|
||||||
it.textureMinFilter = GL_NEAREST
|
1 -> GL_R8
|
||||||
it.textureMagFilter = GL_NEAREST
|
3 -> GL_RGB8
|
||||||
}
|
4 -> GL_RGBA8
|
||||||
|
else -> throw IllegalArgumentException("Unknown amount of channels in $it: ${data.amountOfChannels}")
|
||||||
|
})
|
||||||
|
|
||||||
|
tex.upload(when (data.amountOfChannels) {
|
||||||
|
1 -> GL_RED
|
||||||
|
3 -> GL_RGB
|
||||||
|
4 -> GL_RGBA
|
||||||
|
else -> throw IllegalArgumentException("Unknown amount of channels in $it: ${data.amountOfChannels}")
|
||||||
|
}, GL_UNSIGNED_BYTE, data.data)
|
||||||
|
|
||||||
|
tex.textureMinFilter = GL_NEAREST
|
||||||
|
tex.textureMagFilter = GL_NEAREST
|
||||||
|
|
||||||
|
tex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -489,7 +516,7 @@ class StarboundClient : Closeable {
|
|||||||
|
|
||||||
programs.position.use()
|
programs.position.use()
|
||||||
programs.position.colorMultiplier = color
|
programs.position.colorMultiplier = color
|
||||||
programs.position.worldMatrix = stack.last()
|
programs.position.modelMatrix = stack.last()
|
||||||
|
|
||||||
builder.draw(GL_LINES)
|
builder.draw(GL_LINES)
|
||||||
}
|
}
|
||||||
@ -632,7 +659,9 @@ class StarboundClient : Closeable {
|
|||||||
var viewportLighting = LightCalculator(viewportCells, viewportCellWidth, viewportCellHeight)
|
var viewportLighting = LightCalculator(viewportCells, viewportCellWidth, viewportCellHeight)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
val viewportLightingTexture = newTexture("Viewport Lighting")
|
var viewportLightingTexture = GLTexture2D(1, 1, GL_RGB8)
|
||||||
|
private set
|
||||||
|
|
||||||
private var viewportLightingMem: ByteBuffer? = null
|
private var viewportLightingMem: ByteBuffer? = null
|
||||||
|
|
||||||
fun updateViewportParams() {
|
fun updateViewportParams() {
|
||||||
@ -643,8 +672,14 @@ class StarboundClient : Closeable {
|
|||||||
|
|
||||||
viewportCellX = roundTowardsNegativeInfinity(viewportRectangle.mins.x) - 16
|
viewportCellX = roundTowardsNegativeInfinity(viewportRectangle.mins.x) - 16
|
||||||
viewportCellY = roundTowardsNegativeInfinity(viewportRectangle.mins.y) - 16
|
viewportCellY = roundTowardsNegativeInfinity(viewportRectangle.mins.y) - 16
|
||||||
viewportCellWidth = roundTowardsPositiveInfinity(viewportRectangle.width) + 32
|
val viewportCellWidth0 = roundTowardsPositiveInfinity(viewportRectangle.width) + 32
|
||||||
viewportCellHeight = roundTowardsPositiveInfinity(viewportRectangle.height) + 32
|
val viewportCellHeight0 = roundTowardsPositiveInfinity(viewportRectangle.height) + 32
|
||||||
|
|
||||||
|
if ((viewportCellWidth0 - viewportCellWidth).absoluteValue > 1)
|
||||||
|
viewportCellWidth = viewportCellWidth0
|
||||||
|
|
||||||
|
if ((viewportCellHeight0 - viewportCellHeight).absoluteValue > 1)
|
||||||
|
viewportCellHeight = viewportCellHeight0
|
||||||
|
|
||||||
viewportTopRight = screenToWorld(viewportWidth, viewportHeight)
|
viewportTopRight = screenToWorld(viewportWidth, viewportHeight)
|
||||||
viewportBottomLeft = screenToWorld(0, 0)
|
viewportBottomLeft = screenToWorld(0, 0)
|
||||||
@ -655,6 +690,7 @@ class StarboundClient : Closeable {
|
|||||||
|
|
||||||
if (viewportCellWidth > 0 && viewportCellHeight > 0) {
|
if (viewportCellWidth > 0 && viewportCellHeight > 0) {
|
||||||
viewportLightingMem = ByteBuffer.allocateDirect(viewportCellWidth.coerceAtMost(4096) * viewportCellHeight.coerceAtMost(4096) * 3)
|
viewportLightingMem = ByteBuffer.allocateDirect(viewportCellWidth.coerceAtMost(4096) * viewportCellHeight.coerceAtMost(4096) * 3)
|
||||||
|
viewportLightingTexture = GLTexture2D(viewportCellWidth.coerceAtMost(4096), viewportCellHeight.coerceAtMost(4096), GL_RGB8)
|
||||||
} else {
|
} else {
|
||||||
viewportLightingMem = null
|
viewportLightingMem = null
|
||||||
}
|
}
|
||||||
@ -681,6 +717,8 @@ class StarboundClient : Closeable {
|
|||||||
onPostDrawWorldOnce.add(lambda)
|
onPostDrawWorldOnce.add(lambda)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val layers = LayeredRenderer()
|
||||||
|
|
||||||
fun renderFrame(): Boolean {
|
fun renderFrame(): Boolean {
|
||||||
ensureSameThread()
|
ensureSameThread()
|
||||||
|
|
||||||
@ -711,14 +749,14 @@ class StarboundClient : Closeable {
|
|||||||
cleanup()
|
cleanup()
|
||||||
GLFW.glfwPollEvents()
|
GLFW.glfwPollEvents()
|
||||||
|
|
||||||
if (world != null) {
|
if (world != null && Starbound.initialized)
|
||||||
if (Starbound.initialized)
|
world.think()
|
||||||
world.think()
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layers.clear()
|
||||||
|
|
||||||
uberShaderPrograms.forEachValid {
|
uberShaderPrograms.forEachValid {
|
||||||
if (it.flags.contains(UberShader.Flag.NEEDS_SCREEN_SIZE)) {
|
if (it.flags.contains(UberShader.Flag.NEEDS_SCREEN_SIZE)) {
|
||||||
it.screenSize = Vector2f(viewportWidth.toFloat(), viewportHeight.toFloat())
|
it.screenSize = Vector2f(viewportWidth.toFloat(), viewportHeight.toFloat())
|
||||||
@ -727,7 +765,6 @@ class StarboundClient : Closeable {
|
|||||||
|
|
||||||
if (world != null) {
|
if (world != null) {
|
||||||
updateViewportParams()
|
updateViewportParams()
|
||||||
val layers = LayeredRenderer()
|
|
||||||
|
|
||||||
if (Starbound.initialized)
|
if (Starbound.initialized)
|
||||||
world.think()
|
world.think()
|
||||||
@ -741,7 +778,8 @@ class StarboundClient : Closeable {
|
|||||||
.scale(x = settings.zoom * PIXELS_IN_STARBOUND_UNITf, y = settings.zoom * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера
|
.scale(x = settings.zoom * PIXELS_IN_STARBOUND_UNITf, y = settings.zoom * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера
|
||||||
.translate(-camera.pos.x.toFloat(), -camera.pos.y.toFloat()) // перемещаем вид к камере
|
.translate(-camera.pos.x.toFloat(), -camera.pos.y.toFloat()) // перемещаем вид к камере
|
||||||
|
|
||||||
regularShaderPrograms.forEachValid { it.viewMatrix = viewMatrix }
|
uberShaderPrograms.forEachValid { it.viewMatrix = viewMatrix }
|
||||||
|
fontShaderPrograms.forEachValid { it.viewMatrix = viewMatrix }
|
||||||
|
|
||||||
for (lambda in onPreDrawWorld) {
|
for (lambda in onPreDrawWorld) {
|
||||||
lambda.invoke(layers)
|
lambda.invoke(layers)
|
||||||
@ -763,16 +801,13 @@ class StarboundClient : Closeable {
|
|||||||
viewportLightingMem.position(0)
|
viewportLightingMem.position(0)
|
||||||
BufferUtils.zeroBuffer(viewportLightingMem)
|
BufferUtils.zeroBuffer(viewportLightingMem)
|
||||||
viewportLightingMem.position(0)
|
viewportLightingMem.position(0)
|
||||||
viewportLighting.calculate(viewportLightingMem, viewportLighting.width.coerceAtMost(4096), viewportLighting.height.coerceAtMost(4096))
|
viewportLighting.calculate(viewportLightingMem, viewportLightingTexture.width, viewportLightingTexture.height)
|
||||||
viewportLightingMem.position(0)
|
viewportLightingMem.position(0)
|
||||||
|
|
||||||
val old = textureUnpackAlignment
|
val old = textureUnpackAlignment
|
||||||
textureUnpackAlignment = if (viewportLighting.width.coerceAtMost(4096) % 4 == 0) 4 else 1
|
textureUnpackAlignment = if (viewportLightingTexture.width.coerceAtMost(4096) % 4 == 0) 4 else 1
|
||||||
|
|
||||||
viewportLightingTexture.upload(
|
viewportLightingTexture.upload(
|
||||||
GL_RGB,
|
|
||||||
viewportLighting.width.coerceAtMost(4096),
|
|
||||||
viewportLighting.height.coerceAtMost(4096),
|
|
||||||
GL_RGB,
|
GL_RGB,
|
||||||
GL_UNSIGNED_BYTE,
|
GL_UNSIGNED_BYTE,
|
||||||
viewportLightingMem
|
viewportLightingMem
|
||||||
@ -780,14 +815,11 @@ class StarboundClient : Closeable {
|
|||||||
|
|
||||||
textureUnpackAlignment = old
|
textureUnpackAlignment = old
|
||||||
} else {
|
} else {
|
||||||
viewportLightingTexture.upload(GL_RGBA, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, WHITE)
|
viewportLightingTexture = whiteTexture
|
||||||
}
|
}
|
||||||
|
|
||||||
viewportLightingTexture.textureMinFilter = GL_LINEAR
|
viewportLightingTexture.textureMinFilter = GL_LINEAR
|
||||||
|
textures2D[lightMapLocation] = viewportLightingTexture
|
||||||
activeTexture = lightMapLocation
|
|
||||||
texture2D = viewportLightingTexture
|
|
||||||
activeTexture = 0
|
|
||||||
|
|
||||||
val lightmapUV = if (fullbright) Vector4f.ZERO else Vector4f(
|
val lightmapUV = if (fullbright) Vector4f.ZERO else Vector4f(
|
||||||
((viewportBottomLeft.x - viewportCellX) / viewportLighting.width).toFloat(),
|
((viewportBottomLeft.x - viewportCellX) / viewportLighting.width).toFloat(),
|
||||||
@ -809,7 +841,8 @@ class StarboundClient : Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
regularShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen }
|
uberShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen }
|
||||||
|
fontShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen }
|
||||||
|
|
||||||
val thisTime = System.currentTimeMillis()
|
val thisTime = System.currentTimeMillis()
|
||||||
|
|
||||||
@ -847,7 +880,7 @@ class StarboundClient : Closeable {
|
|||||||
font.render("Latency: ${(averageRenderWait * 1_00000.0).toInt() / 100f}ms", scale = 0.4f)
|
font.render("Latency: ${(averageRenderWait * 1_00000.0).toInt() / 100f}ms", scale = 0.4f)
|
||||||
font.render("Frame: ${(averageRenderTime * 1_00000.0).toInt() / 100f}ms", y = font.lineHeight * 0.6f, scale = 0.4f)
|
font.render("Frame: ${(averageRenderTime * 1_00000.0).toInt() / 100f}ms", y = font.lineHeight * 0.6f, scale = 0.4f)
|
||||||
font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = font.lineHeight * 1.2f, scale = 0.4f)
|
font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = font.lineHeight * 1.2f, scale = 0.4f)
|
||||||
font.render("OGL A: ${objectsCreated - objectsCleaned} D: $objectsCleaned", y = font.lineHeight * 1.8f, scale = 0.4f)
|
font.render("OGL C: $objectsCreated D: $objectsCleaned A: ${objectsCreated - objectsCleaned}", y = font.lineHeight * 1.8f, scale = 0.4f)
|
||||||
|
|
||||||
GLFW.glfwSwapBuffers(window)
|
GLFW.glfwSwapBuffers(window)
|
||||||
GLFW.glfwPollEvents()
|
GLFW.glfwPollEvents()
|
||||||
|
@ -9,10 +9,12 @@ sealed class BufferObject : GLObject() {
|
|||||||
final override val client = StarboundClient.current()
|
final override val client = StarboundClient.current()
|
||||||
abstract val glType: Int
|
abstract val glType: Int
|
||||||
|
|
||||||
|
@Deprecated("Has no effect", level = DeprecationLevel.ERROR)
|
||||||
override fun bind() {
|
override fun bind() {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Has no effect", level = DeprecationLevel.ERROR)
|
||||||
override fun unbind() {
|
override fun unbind() {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,7 @@ class GLFrameBuffer : GLObject() {
|
|||||||
throw IllegalStateException("Already has texture attached")
|
throw IllegalStateException("Already has texture attached")
|
||||||
}
|
}
|
||||||
|
|
||||||
val texture = GLTexture2D("framebuffer_$pointer")
|
val texture = GLTexture2D(width, height, format)
|
||||||
texture.allocate(format, width, height)
|
|
||||||
|
|
||||||
val old = client.framebuffer
|
val old = client.framebuffer
|
||||||
bind()
|
bind()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.gl
|
package ru.dbotthepony.kstarbound.client.gl
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import org.lwjgl.opengl.GL11
|
||||||
import org.lwjgl.opengl.GL45.*
|
import org.lwjgl.opengl.GL45.*
|
||||||
import org.lwjgl.stb.STBImage
|
import org.lwjgl.stb.STBImage
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
@ -30,24 +31,23 @@ private class GLTexturePropertyTracker(private val flag: Int, private var value:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("SameParameterValue")
|
@Suppress("SameParameterValue")
|
||||||
class GLTexture2D(val name: String = "<unknown>") : GLObject() {
|
class GLTexture2D(val width: Int, val height: Int, val format: Int, val levels: Int = 1) : GLObject() {
|
||||||
|
init {
|
||||||
|
require(width > 0 && height > 0) { "Invalid image size: $width x $height" }
|
||||||
|
require(levels > 0) { "Invalid image level count: $levels" }
|
||||||
|
}
|
||||||
|
|
||||||
override val client = StarboundClient.current()
|
override val client = StarboundClient.current()
|
||||||
override val pointer = glCreateTextures(GL_TEXTURE_2D)
|
override val pointer = glCreateTextures(GL_TEXTURE_2D)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
checkForGLError()
|
checkForGLError("Creating texture")
|
||||||
client.registerCleanable(this, ::glDeleteTextures, pointer)
|
client.registerCleanable(this, ::glDeleteTextures, pointer)
|
||||||
|
|
||||||
|
glTextureStorage2D(pointer, levels, format, width, height)
|
||||||
|
checkForGLError("Invalid image format: $format")
|
||||||
}
|
}
|
||||||
|
|
||||||
var width = 0
|
|
||||||
private set
|
|
||||||
|
|
||||||
var height = 0
|
|
||||||
private set
|
|
||||||
|
|
||||||
var uploaded = false
|
|
||||||
private set
|
|
||||||
|
|
||||||
val aspectRatioWH: Float get() {
|
val aspectRatioWH: Float get() {
|
||||||
if (height == 0) {
|
if (height == 0) {
|
||||||
return 1f
|
return 1f
|
||||||
@ -71,13 +71,14 @@ class GLTexture2D(val name: String = "<unknown>") : GLObject() {
|
|||||||
var textureWrapS by GLTexturePropertyTracker(GL_TEXTURE_WRAP_S, GL_REPEAT)
|
var textureWrapS by GLTexturePropertyTracker(GL_TEXTURE_WRAP_S, GL_REPEAT)
|
||||||
var textureWrapT by GLTexturePropertyTracker(GL_TEXTURE_WRAP_T, GL_REPEAT)
|
var textureWrapT by GLTexturePropertyTracker(GL_TEXTURE_WRAP_T, GL_REPEAT)
|
||||||
|
|
||||||
|
@Deprecated("Has no effect", level = DeprecationLevel.ERROR)
|
||||||
override fun bind() {
|
override fun bind() {
|
||||||
client.texture2D = this
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Has no effect", level = DeprecationLevel.ERROR)
|
||||||
override fun unbind() {
|
override fun unbind() {
|
||||||
if (client.texture2D === this)
|
// do nothing
|
||||||
client.texture2D = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generateMips(): GLTexture2D {
|
fun generateMips(): GLTexture2D {
|
||||||
@ -88,186 +89,31 @@ class GLTexture2D(val name: String = "<unknown>") : GLObject() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun pixelToUV(x: Float, y: Float): UVCoord {
|
fun pixelToUV(x: Float, y: Float): UVCoord {
|
||||||
check(uploaded) { "Texture is not uploaded to be used" }
|
|
||||||
return UVCoord(x / width, y / height)
|
return UVCoord(x / width, y / height)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pixelToUV(x: Int, y: Int): UVCoord {
|
fun pixelToUV(x: Int, y: Int): UVCoord {
|
||||||
check(uploaded) { "Texture is not uploaded to be used" }
|
|
||||||
return UVCoord(x.toFloat() / width, y.toFloat() / height)
|
return UVCoord(x.toFloat() / width, y.toFloat() / height)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pixelToUV(pos: Vector2i) = pixelToUV(pos.x, pos.y)
|
fun pixelToUV(pos: Vector2i) = pixelToUV(pos.x, pos.y)
|
||||||
|
|
||||||
fun allocate(mipmap: Int, loadedFormat: Int, width: Int, height: Int): GLTexture2D {
|
fun upload(level: Int, bufferFormat: Int, dataFormat: Int, data: ByteBuffer): GLTexture2D {
|
||||||
bind()
|
return upload(level, 0, 0, width, height, bufferFormat, dataFormat, data)
|
||||||
|
|
||||||
require(width > 0) { "Invalid width $width" }
|
|
||||||
require(height > 0) { "Invalid height $height" }
|
|
||||||
this.width = width
|
|
||||||
this.height = height
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, mipmap, loadedFormat, width, height, 0, loadedFormat, GL_UNSIGNED_BYTE, 0L)
|
|
||||||
checkForGLError()
|
|
||||||
uploaded = true
|
|
||||||
return this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allocate(loadedFormat: Int, width: Int, height: Int): GLTexture2D {
|
fun upload(bufferFormat: Int, dataFormat: Int, data: ByteBuffer): GLTexture2D {
|
||||||
return allocate(0, loadedFormat, width, height)
|
return upload(0, 0, 0, width, height, bufferFormat, dataFormat, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun upload(mipmap: Int, loadedFormat: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: IntArray): GLTexture2D {
|
fun upload(level: Int, x: Int, y: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: ByteBuffer): GLTexture2D {
|
||||||
bind()
|
require(x >= 0 && y >= 0 && width > 0 && height > 0 && x + width <= this.width && y + height <= this.height) {
|
||||||
|
"Bad texture rectangle specified: $x x $y -> ${x + width} x ${y + height}, which is out of bounds for texture ${this.width} x ${this.height}"
|
||||||
require(width > 0) { "Invalid width $width" }
|
|
||||||
require(height > 0) { "Invalid height $height" }
|
|
||||||
this.width = width
|
|
||||||
this.height = height
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, mipmap, loadedFormat, width, height, 0, bufferFormat, dataFormat, data)
|
|
||||||
checkForGLError()
|
|
||||||
uploaded = true
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun upload(mipmap: Int, memoryFormat: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: ByteBuffer): GLTexture2D {
|
|
||||||
bind()
|
|
||||||
|
|
||||||
require(width > 0) { "Invalid width $width" }
|
|
||||||
require(height > 0) { "Invalid height $height" }
|
|
||||||
|
|
||||||
this.width = width
|
|
||||||
this.height = height
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, mipmap, memoryFormat, width, height, 0, bufferFormat, dataFormat, data)
|
|
||||||
checkForGLError()
|
|
||||||
uploaded = true
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun upload(memoryFormat: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: IntArray): GLTexture2D {
|
|
||||||
return upload(0, memoryFormat, width, height, bufferFormat, dataFormat, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun upload(memoryFormat: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: ByteBuffer): GLTexture2D {
|
|
||||||
return upload(0, memoryFormat, width, height, bufferFormat, dataFormat, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun upload(path: File, memoryFormat: Int, bufferFormat: Int): GLTexture2D {
|
|
||||||
client.ensureSameThread()
|
|
||||||
|
|
||||||
if (!path.exists()) {
|
|
||||||
throw FileNotFoundException("${path.absolutePath} does not exist")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!path.isFile) {
|
require(level in 0 until levels) { "Level out of bounds: $level" }
|
||||||
throw FileNotFoundException("${path.absolutePath} is not a file")
|
glTextureSubImage2D(pointer, level, x, y, width, height, bufferFormat, dataFormat, data)
|
||||||
}
|
checkForGLError("Uploading texture data")
|
||||||
|
|
||||||
val getwidth = intArrayOf(0)
|
|
||||||
val getheight = intArrayOf(0)
|
|
||||||
val getchannels = intArrayOf(0)
|
|
||||||
|
|
||||||
val bytes = STBImage.stbi_load(path.absolutePath, getwidth, getheight, getchannels, 0) ?: throw TextureLoadingException("Unable to load ${path.absolutePath}. Is it a valid image?")
|
|
||||||
|
|
||||||
require(getwidth[0] > 0) { "Image ${path.absolutePath} has bad width of ${getwidth[0]}" }
|
|
||||||
require(getheight[0] > 0) { "Image ${path.absolutePath} has bad height of ${getheight[0]}" }
|
|
||||||
|
|
||||||
upload(memoryFormat, getwidth[0], getheight[0], bufferFormat, GL_UNSIGNED_BYTE, bytes)
|
|
||||||
STBImage.stbi_image_free(bytes)
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun upload(path: File): GLTexture2D {
|
|
||||||
client.ensureSameThread()
|
|
||||||
|
|
||||||
if (!path.exists()) {
|
|
||||||
throw FileNotFoundException("${path.absolutePath} does not exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!path.isFile) {
|
|
||||||
throw FileNotFoundException("${path.absolutePath} is not a file")
|
|
||||||
}
|
|
||||||
|
|
||||||
val getwidth = intArrayOf(0)
|
|
||||||
val getheight = intArrayOf(0)
|
|
||||||
val getchannels = intArrayOf(0)
|
|
||||||
|
|
||||||
val bytes = STBImage.stbi_load(path.absolutePath, getwidth, getheight, getchannels, 0) ?: throw TextureLoadingException("Unable to load ${path.absolutePath}. Is it a valid image?")
|
|
||||||
|
|
||||||
require(getwidth[0] > 0) { "Image ${path.absolutePath} has bad width of ${getwidth[0]}" }
|
|
||||||
require(getheight[0] > 0) { "Image ${path.absolutePath} has bad height of ${getheight[0]}" }
|
|
||||||
|
|
||||||
val bufferFormat = when (val numChannels = getchannels[0]) {
|
|
||||||
1 -> GL_R
|
|
||||||
2 -> GL_RG
|
|
||||||
3 -> GL_RGB
|
|
||||||
4 -> GL_RGBA
|
|
||||||
else -> throw IllegalArgumentException("Weird amount of channels in file: $numChannels")
|
|
||||||
}
|
|
||||||
|
|
||||||
upload(bufferFormat, getwidth[0], getheight[0], bufferFormat, GL_UNSIGNED_BYTE, bytes)
|
|
||||||
STBImage.stbi_image_free(bytes)
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun upload(buff: ByteBuffer, memoryFormat: Int, bufferFormat: Int): GLTexture2D {
|
|
||||||
client.ensureSameThread()
|
|
||||||
|
|
||||||
val getwidth = intArrayOf(0)
|
|
||||||
val getheight = intArrayOf(0)
|
|
||||||
val getchannels = intArrayOf(0)
|
|
||||||
|
|
||||||
val bytes = STBImage.stbi_load_from_memory(buff, getwidth, getheight, getchannels, 0) ?: throw TextureLoadingException("Unable to load ${buff}. Is it a valid image?")
|
|
||||||
|
|
||||||
require(getwidth[0] > 0) { "Image '$name' has bad width of ${getwidth[0]}" }
|
|
||||||
require(getheight[0] > 0) { "Image '$name' has bad height of ${getheight[0]}" }
|
|
||||||
|
|
||||||
upload(memoryFormat, getwidth[0], getheight[0], bufferFormat, GL_UNSIGNED_BYTE, bytes)
|
|
||||||
STBImage.stbi_image_free(bytes)
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun upload(buff: ByteBuffer): GLTexture2D {
|
|
||||||
client.ensureSameThread()
|
|
||||||
|
|
||||||
val getwidth = intArrayOf(0)
|
|
||||||
val getheight = intArrayOf(0)
|
|
||||||
val getchannels = intArrayOf(0)
|
|
||||||
|
|
||||||
val bytes = STBImage.stbi_load_from_memory(buff, getwidth, getheight, getchannels, 0) ?: throw TextureLoadingException("Unable to load ${buff}. Is it a valid image?")
|
|
||||||
|
|
||||||
require(getwidth[0] > 0) { "Image '$name' has bad width of ${getwidth[0]}" }
|
|
||||||
require(getheight[0] > 0) { "Image '$name' has bad height of ${getheight[0]}" }
|
|
||||||
|
|
||||||
val bufferFormat = when (val numChannels = getchannels[0]) {
|
|
||||||
1 -> GL_R
|
|
||||||
2 -> GL_RG
|
|
||||||
3 -> GL_RGB
|
|
||||||
4 -> GL_RGBA
|
|
||||||
else -> throw IllegalArgumentException("Weird amount of channels in file: $numChannels")
|
|
||||||
}
|
|
||||||
|
|
||||||
upload(bufferFormat, getwidth[0], getheight[0], bufferFormat, GL_UNSIGNED_BYTE, bytes)
|
|
||||||
STBImage.stbi_image_free(bytes)
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun upload(data: Image): GLTexture2D {
|
|
||||||
client.ensureSameThread()
|
|
||||||
|
|
||||||
val bufferFormat = when (val numChannels = data.amountOfChannels) {
|
|
||||||
1 -> GL_R
|
|
||||||
2 -> GL_RG
|
|
||||||
3 -> GL_RGB
|
|
||||||
4 -> GL_RGBA
|
|
||||||
else -> throw IllegalArgumentException("Weird amount of channels in file: $numChannels")
|
|
||||||
}
|
|
||||||
|
|
||||||
upload(bufferFormat, data.width, data.height, bufferFormat, GL_UNSIGNED_BYTE, data.data)
|
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.client.gl.properties
|
||||||
|
|
||||||
|
import org.lwjgl.opengl.GL45
|
||||||
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
|
import ru.dbotthepony.kstarbound.client.gl.GLObject
|
||||||
|
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
|
||||||
|
|
||||||
|
class GLTexturesTracker<T : GLObject>(maxValue: Int) {
|
||||||
|
private val client = StarboundClient.current()
|
||||||
|
private val values = arrayOfNulls<GLObject>(maxValue)
|
||||||
|
|
||||||
|
operator fun get(index: Int): T? {
|
||||||
|
return values[index] as T?
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun set(index: Int, value: T?) {
|
||||||
|
client.ensureSameThread()
|
||||||
|
require(value == null || client === value.client) { "$value does not belong to $client" }
|
||||||
|
|
||||||
|
if (values[index] === value)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
GL45.glBindTextureUnit(index, 0)
|
||||||
|
checkForGLError()
|
||||||
|
} else {
|
||||||
|
GL45.glBindTextureUnit(index, value.pointer)
|
||||||
|
checkForGLError()
|
||||||
|
}
|
||||||
|
|
||||||
|
values[index] = value
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -28,13 +28,6 @@ open class GLShaderProgram(
|
|||||||
shaders: Iterable<GLShader>,
|
shaders: Iterable<GLShader>,
|
||||||
val attributes: VertexAttributes
|
val attributes: VertexAttributes
|
||||||
) : GLObject() {
|
) : GLObject() {
|
||||||
interface Regular {
|
|
||||||
var viewMatrix: Matrix3f // set before rendering anything (camera and/or projection)
|
|
||||||
var worldMatrix: Matrix3f // global matrix stack
|
|
||||||
var modelMatrix: Matrix3f // should be set by drawing code itself
|
|
||||||
var colorMultiplier: IStruct4f
|
|
||||||
}
|
|
||||||
|
|
||||||
final override val client = StarboundClient.current()
|
final override val client = StarboundClient.current()
|
||||||
final override val pointer = glCreateProgram()
|
final override val pointer = glCreateProgram()
|
||||||
|
|
||||||
|
@ -22,38 +22,10 @@ private fun shaders1(name: String): List<GLShader> {
|
|||||||
return listOf(client.internalVertex("shaders/vertex/$name.vsh"), client.internalFragment("shaders/fragment/$name.fsh"))
|
return listOf(client.internalVertex("shaders/vertex/$name.vsh"), client.internalFragment("shaders/fragment/$name.fsh"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fontShaders(): List<GLShader> {
|
class FontProgram : GLShaderProgram(shaders1("font"), FONT_VERTEX_FORMAT) {
|
||||||
val client = StarboundClient.current()
|
var viewMatrix: Matrix3f by F3x3Uniform("viewMatrix")
|
||||||
var read = StarboundClient.readInternal("shaders/fragment/font.fsh")
|
var modelMatrix: Matrix3f by F3x3Uniform("modelMatrix")
|
||||||
|
var colorMultiplier: IStruct4f by F4Uniform("colorMultiplier")
|
||||||
val textures = ArrayList<String>()
|
|
||||||
val cases = ArrayList<String>()
|
|
||||||
val cases2 = ArrayList<String>()
|
|
||||||
|
|
||||||
for (i in 0 until client.maxUserTextureBlocks) {
|
|
||||||
cases.add("\tvec4 colorResult$i = colorMultiplier * texture(texture$i, uvOut).r;")
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i in 0 until client.maxUserTextureBlocks) {
|
|
||||||
textures.add("layout (binding=$i) uniform sampler2D texture$i;")
|
|
||||||
cases2.add("if (textureIndexOut < ${i + 0.1f}) { colorResult = colorResult$i; }")
|
|
||||||
}
|
|
||||||
|
|
||||||
cases2.add(" { colorResult = colorMultiplier; }")
|
|
||||||
|
|
||||||
read = read.replace("uniform sampler2D texture0;", textures.joinToString("\n")).replace("\tcolorResult = colorMultiplier * texture(texture0, uvOut).r;", cases.joinToString("\n") + cases2.joinToString("\n\telse "))
|
|
||||||
|
|
||||||
val fragment = GLShader(read, GL_FRAGMENT_SHADER)
|
|
||||||
val vertex = GLShader(StarboundClient.readInternal("shaders/vertex/font.vsh"), GL_VERTEX_SHADER)
|
|
||||||
|
|
||||||
return listOf(fragment, vertex)
|
|
||||||
}
|
|
||||||
|
|
||||||
class FontProgram : GLShaderProgram(shaders1("font"), FONT_VERTEX_FORMAT), GLShaderProgram.Regular {
|
|
||||||
override var viewMatrix: Matrix3f by F3x3Uniform("viewMatrix")
|
|
||||||
override var worldMatrix: Matrix3f by F3x3Uniform("worldMatrix")
|
|
||||||
override var modelMatrix: Matrix3f by F3x3Uniform("modelMatrix", true)
|
|
||||||
override var colorMultiplier: IStruct4f by F4Uniform("colorMultiplier")
|
|
||||||
|
|
||||||
var texture by IUniform("texture0")
|
var texture by IUniform("texture0")
|
||||||
|
|
||||||
|
@ -2,33 +2,37 @@ package ru.dbotthepony.kstarbound.client.gl.shader
|
|||||||
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||||
|
import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction
|
||||||
|
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
||||||
import org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER
|
import org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER
|
||||||
import org.lwjgl.opengl.GL20.GL_VERTEX_SHADER
|
import org.lwjgl.opengl.GL20.GL_VERTEX_SHADER
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
|
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
|
||||||
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
|
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
|
||||||
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributeType
|
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributeType
|
||||||
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes
|
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes
|
||||||
|
import ru.dbotthepony.kstarbound.client.render.RenderConfig
|
||||||
import ru.dbotthepony.kvector.api.IStruct4f
|
import ru.dbotthepony.kvector.api.IStruct4f
|
||||||
import ru.dbotthepony.kvector.arrays.Matrix3f
|
import ru.dbotthepony.kvector.arrays.Matrix3f
|
||||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
class UberShader private constructor(
|
class UberShader private constructor(
|
||||||
fragment: GLShader,
|
fragment: GLShader,
|
||||||
vertex: GLShader,
|
vertex: GLShader,
|
||||||
attributes: VertexAttributes,
|
attributes: VertexAttributes,
|
||||||
val flags: Set<Flag>,
|
val flags: Set<Flag>,
|
||||||
) : GLShaderProgram(listOf(fragment, vertex), attributes), GLShaderProgram.Regular {
|
) : GLShaderProgram(listOf(fragment, vertex), attributes) {
|
||||||
enum class Flag {
|
enum class Flag {
|
||||||
LIGHT_MAP,
|
LIGHT_MAP,
|
||||||
NEEDS_SCREEN_SIZE;
|
NEEDS_SCREEN_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
override var viewMatrix: Matrix3f by F3x3Uniform("viewMatrix")
|
var viewMatrix: Matrix3f by F3x3Uniform("viewMatrix")
|
||||||
override var worldMatrix: Matrix3f by F3x3Uniform("worldMatrix")
|
var modelMatrix: Matrix3f by F3x3Uniform("modelMatrix")
|
||||||
override var modelMatrix: Matrix3f by F3x3Uniform("modelMatrix")
|
var colorMultiplier: IStruct4f by F4Uniform("colorMultiplier")
|
||||||
override var colorMultiplier: IStruct4f by F4Uniform("colorMultiplier")
|
|
||||||
|
|
||||||
var texture0 by IUniform("texture0", VertexAttributeType.UV !in attributes)
|
var texture0 by IUniform("texture0", VertexAttributeType.UV !in attributes)
|
||||||
|
|
||||||
@ -40,11 +44,24 @@ class UberShader private constructor(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
viewMatrix = Matrix3f.identity()
|
viewMatrix = Matrix3f.identity()
|
||||||
worldMatrix = Matrix3f.identity()
|
|
||||||
modelMatrix = Matrix3f.identity()
|
modelMatrix = Matrix3f.identity()
|
||||||
colorMultiplier = RGBAColor.WHITE
|
colorMultiplier = RGBAColor.WHITE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val textureConfigs = Reference2ObjectOpenHashMap<GLTexture2D, RenderConfig<UberShader>>()
|
||||||
|
|
||||||
|
fun config(texture: GLTexture2D): RenderConfig<UberShader> {
|
||||||
|
return textureConfigs.computeIfAbsent(texture, Reference2ObjectFunction {
|
||||||
|
object : RenderConfig<UberShader>(this) {
|
||||||
|
override fun setup() {
|
||||||
|
super.setup()
|
||||||
|
|
||||||
|
client.textures2D[0] = texture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
private val directives = Object2ObjectArrayMap<String, String>()
|
private val directives = Object2ObjectArrayMap<String, String>()
|
||||||
private val attributes = ObjectArraySet<VertexAttributeType>()
|
private val attributes = ObjectArraySet<VertexAttributeType>()
|
||||||
|
@ -12,7 +12,7 @@ import ru.dbotthepony.kstarbound.client.gl.checkForGLError
|
|||||||
class StreamVertexBuilder(
|
class StreamVertexBuilder(
|
||||||
attributes: VertexAttributes,
|
attributes: VertexAttributes,
|
||||||
type: GeometryType? = null,
|
type: GeometryType? = null,
|
||||||
initialCapacity: Int = 64,
|
initialCapacity: Int = 32,
|
||||||
) {
|
) {
|
||||||
val state = StarboundClient.current()
|
val state = StarboundClient.current()
|
||||||
val builder = VertexBuilder(attributes, type, initialCapacity)
|
val builder = VertexBuilder(attributes, type, initialCapacity)
|
||||||
|
@ -51,7 +51,7 @@ private fun indexSize(type: Int): Int {
|
|||||||
class VertexBuilder(
|
class VertexBuilder(
|
||||||
val attributes: VertexAttributes,
|
val attributes: VertexAttributes,
|
||||||
val defaultMode: GeometryType? = null,
|
val defaultMode: GeometryType? = null,
|
||||||
initialCapacity: Int = 64
|
initialCapacity: Int = 32
|
||||||
) {
|
) {
|
||||||
constructor(attributes: VertexAttributes, initialCapacity: Int) : this(attributes, null, initialCapacity)
|
constructor(attributes: VertexAttributes, initialCapacity: Int) : this(attributes, null, initialCapacity)
|
||||||
|
|
||||||
@ -88,6 +88,14 @@ class VertexBuilder(
|
|||||||
var elementCount = 0 // GeometryType specific (quads, triangles, lines), less than or equal (points) to vertexCount
|
var elementCount = 0 // GeometryType specific (quads, triangles, lines), less than or equal (points) to vertexCount
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
fun isEmpty(): Boolean {
|
||||||
|
return !inVertex && vertexCount == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isNotEmpty(): Boolean {
|
||||||
|
return inVertex || vertexCount != 0
|
||||||
|
}
|
||||||
|
|
||||||
private fun ensureCapacity() {
|
private fun ensureCapacity() {
|
||||||
val mode = mode ?: return
|
val mode = mode ?: return
|
||||||
|
|
||||||
@ -177,8 +185,12 @@ class VertexBuilder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
check(elementVertices == 0) { "Can't change buffer geometry type while not having fully built current element" }
|
check(elementVertices == 0) { "Can't change buffer geometry type while not having fully built current element" }
|
||||||
this.mode = mode
|
|
||||||
ensureCapacity()
|
if (this.mode !== mode) {
|
||||||
|
this.mode = mode
|
||||||
|
ensureCapacity()
|
||||||
|
}
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,8 +43,7 @@ class Box2DRenderer : IDebugDraw {
|
|||||||
|
|
||||||
state.programs.position.use()
|
state.programs.position.use()
|
||||||
state.programs.position.colorMultiplier = color
|
state.programs.position.colorMultiplier = color
|
||||||
state.programs.position.worldMatrix = state.stack.last()
|
state.programs.position.modelMatrix = state.stack.last()
|
||||||
state.programs.position.modelMatrix = identity
|
|
||||||
|
|
||||||
builder.draw(GL_LINES)
|
builder.draw(GL_LINES)
|
||||||
}
|
}
|
||||||
@ -68,8 +67,7 @@ class Box2DRenderer : IDebugDraw {
|
|||||||
|
|
||||||
state.programs.position.use()
|
state.programs.position.use()
|
||||||
state.programs.position.colorMultiplier = color
|
state.programs.position.colorMultiplier = color
|
||||||
state.programs.position.worldMatrix = state.stack.last()
|
state.programs.position.modelMatrix = state.stack.last()
|
||||||
state.programs.position.modelMatrix = identity
|
|
||||||
|
|
||||||
builder.draw(GL_TRIANGLES)
|
builder.draw(GL_TRIANGLES)
|
||||||
}
|
}
|
||||||
|
@ -98,16 +98,13 @@ class Font(
|
|||||||
Vector2i(roundTowardsPositiveInfinity(face.nativeMemory.bbox.xMax.toInt() / (12.0 * 48.0 / size)), roundTowardsPositiveInfinity(face.nativeMemory.bbox.yMax.toInt() / (12.0 * 48.0 / size))),
|
Vector2i(roundTowardsPositiveInfinity(face.nativeMemory.bbox.xMax.toInt() / (12.0 * 48.0 / size)), roundTowardsPositiveInfinity(face.nativeMemory.bbox.yMax.toInt() / (12.0 * 48.0 / size))),
|
||||||
)
|
)
|
||||||
|
|
||||||
private val atlas = GLTexture2D()
|
|
||||||
private val atlasWidth: Int = glGetInternalformati(GL_TEXTURE_2D, GL_RED, GL_MAX_WIDTH).coerceAtMost(4096)
|
private val atlasWidth: Int = glGetInternalformati(GL_TEXTURE_2D, GL_RED, GL_MAX_WIDTH).coerceAtMost(4096)
|
||||||
private val atlasHeight: Int = glGetInternalformati(GL_TEXTURE_2D, GL_RED, GL_MAX_HEIGHT).coerceAtMost(4096)
|
private val atlasHeight: Int = glGetInternalformati(GL_TEXTURE_2D, GL_RED, GL_MAX_HEIGHT).coerceAtMost(4096)
|
||||||
private var nextAtlasX = 0
|
private var nextAtlasX = 0
|
||||||
private var nextAtlasY = 0
|
private var nextAtlasY = 0
|
||||||
|
private val atlas = GLTexture2D(atlasWidth, atlasHeight, GL_R8)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
glTextureStorage2D(atlas.pointer, 1, GL_R8, atlasWidth, atlasHeight)
|
|
||||||
checkForGLError()
|
|
||||||
|
|
||||||
atlas.textureMinFilter = GL_LINEAR
|
atlas.textureMinFilter = GL_LINEAR
|
||||||
atlas.textureMagFilter = GL_LINEAR
|
atlas.textureMagFilter = GL_LINEAR
|
||||||
atlas.textureWrapS = GL_CLAMP_TO_EDGE
|
atlas.textureWrapS = GL_CLAMP_TO_EDGE
|
||||||
@ -217,12 +214,11 @@ class Font(
|
|||||||
if (builder.elementCount != 0) {
|
if (builder.elementCount != 0) {
|
||||||
client.programs.font.use()
|
client.programs.font.use()
|
||||||
client.programs.font.colorMultiplier = color
|
client.programs.font.colorMultiplier = color
|
||||||
client.programs.font.worldMatrix = client.stack.last()
|
client.programs.font.modelMatrix = client.stack.last()
|
||||||
|
|
||||||
builder.upload(vbo, ebo, GL_STREAM_DRAW)
|
builder.upload(vbo, ebo, GL_STREAM_DRAW)
|
||||||
|
|
||||||
client.activeTexture = 0
|
client.textures2D[0] = atlas
|
||||||
atlas.bind()
|
|
||||||
|
|
||||||
client.vao = vao
|
client.vao = vao
|
||||||
glDrawElements(GL_TRIANGLES, builder.indexCount, builder.indexType, 0L)
|
glDrawElements(GL_TRIANGLES, builder.indexCount, builder.indexType, 0L)
|
||||||
@ -341,8 +337,7 @@ class Font(
|
|||||||
try {
|
try {
|
||||||
uv = Vector4f(nextAtlasX / atlasWidth.toFloat(), nextAtlasY / atlasHeight.toFloat(), (nextAtlasX + bitmap.width) / atlasWidth.toFloat(), (nextAtlasY + bitmap.rows) / atlasHeight.toFloat())
|
uv = Vector4f(nextAtlasX / atlasWidth.toFloat(), nextAtlasY / atlasHeight.toFloat(), (nextAtlasX + bitmap.width) / atlasWidth.toFloat(), (nextAtlasY + bitmap.rows) / atlasHeight.toFloat())
|
||||||
|
|
||||||
glTextureSubImage2D(atlas.pointer, 0, nextAtlasX, nextAtlasY, bitmap.width, bitmap.rows, GL_RED, GL_UNSIGNED_BYTE, buff)
|
atlas.upload(0, nextAtlasX, nextAtlasY, bitmap.width, bitmap.rows, GL_RED, GL_UNSIGNED_BYTE, buff)
|
||||||
checkForGLError("Uploading glyph texture data")
|
|
||||||
|
|
||||||
nextAtlasX += bbox.width
|
nextAtlasX += bbox.width
|
||||||
|
|
||||||
|
@ -1,28 +1,116 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.render
|
package ru.dbotthepony.kstarbound.client.render
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap
|
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap
|
||||||
import ru.dbotthepony.kvector.arrays.Matrix3fStack
|
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
|
||||||
import ru.dbotthepony.kvector.arrays.Matrix4fStack
|
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
/**
|
interface IGeometryLayer {
|
||||||
* Позволяет вызывать отрисовщики в определённой (послойной) последовательности
|
fun add(renderer: () -> Unit)
|
||||||
*/
|
fun getBuilder(config: RenderConfig<*>): VertexBuilder
|
||||||
class LayeredRenderer {
|
fun render()
|
||||||
private val layers = Long2ObjectAVLTreeMap<ArrayList<() -> Unit>>()
|
fun clear()
|
||||||
|
fun bakeIntoMeshes(): Stream<Pair<ConfiguredMesh<*>, RenderLayer.Point>>
|
||||||
|
}
|
||||||
|
|
||||||
fun add(layer: Long, renderer: () -> Unit) {
|
object OneShotGeometryLayer : IGeometryLayer {
|
||||||
layers.computeIfAbsent(layer, Function { ArrayList() }).add(renderer)
|
override fun add(renderer: () -> Unit) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun render() {
|
override fun getBuilder(config: RenderConfig<*>): VertexBuilder {
|
||||||
for (list in layers.values) {
|
TODO("Not yet implemented")
|
||||||
for (renderer in list) {
|
}
|
||||||
renderer.invoke()
|
|
||||||
|
override fun render() {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bakeIntoMeshes(): Stream<Pair<ConfiguredMesh<*>, RenderLayer.Point>> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LayeredRenderer {
|
||||||
|
class Layer(val layer: RenderLayer.Point) : IGeometryLayer {
|
||||||
|
private val meshes = ArrayList<ConfiguredMesh<*>>()
|
||||||
|
private val callbacks = ArrayList<() -> Unit>()
|
||||||
|
private val builders = Reference2ObjectLinkedOpenHashMap<RenderConfig<*>, StreamVertexBuilder>()
|
||||||
|
|
||||||
|
override fun add(renderer: () -> Unit) {
|
||||||
|
callbacks.add(renderer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBuilder(config: RenderConfig<*>): VertexBuilder {
|
||||||
|
return builders.computeIfAbsent(config, Function {
|
||||||
|
StreamVertexBuilder(config.program.attributes, initialCapacity = config.initialBuilderCapacity)
|
||||||
|
}).builder
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun render() {
|
||||||
|
builders.entries.forEach {
|
||||||
|
if (it.value.builder.isNotEmpty()) {
|
||||||
|
it.key.setup()
|
||||||
|
it.value.upload()
|
||||||
|
it.value.draw()
|
||||||
|
it.key.uninstall()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
meshes.forEach {
|
||||||
|
it.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks.forEach {
|
||||||
|
it.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layers.clear()
|
override fun clear() {
|
||||||
|
builders.values.forEach { it.builder.begin() }
|
||||||
|
meshes.clear()
|
||||||
|
callbacks.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bakeIntoMeshes(): Stream<Pair<ConfiguredMesh<*>, RenderLayer.Point>> {
|
||||||
|
if (meshes.isNotEmpty() || callbacks.isNotEmpty())
|
||||||
|
throw IllegalStateException("This layer (index $layer) contains preconfigured meshes and/or callbacks")
|
||||||
|
|
||||||
|
return builders.entries.stream()
|
||||||
|
.filter { it.value.builder.isNotEmpty() }
|
||||||
|
.map { ConfiguredMesh(it.key, Mesh(it.value.builder)) to layer }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val layers = Object2ObjectAVLTreeMap<RenderLayer.Point, Layer>()
|
||||||
|
|
||||||
|
fun getBuilder(layer: RenderLayer.Point, config: RenderConfig<*>): VertexBuilder {
|
||||||
|
return getLayer(layer).getBuilder(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bakeIntoMeshes(): Stream<Pair<ConfiguredMesh<*>, RenderLayer.Point>> {
|
||||||
|
return layers.values.stream().flatMap { it.bakeIntoMeshes() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(layer: RenderLayer.Point, renderer: () -> Unit) {
|
||||||
|
getLayer(layer).add(renderer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLayer(layer: RenderLayer.Point): Layer {
|
||||||
|
return layers.computeIfAbsent(layer, Function { Layer(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render() {
|
||||||
|
layers.values.forEach { it.render() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
layers.values.forEach { it.clear() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,9 @@ import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram
|
|||||||
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
|
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
|
||||||
import ru.dbotthepony.kvector.arrays.Matrix4f
|
import ru.dbotthepony.kvector.arrays.Matrix4f
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mesh is container for paired VAO, VBO and EBO, and metadata for them
|
||||||
|
*/
|
||||||
class Mesh() {
|
class Mesh() {
|
||||||
constructor(builder: VertexBuilder) : this() {
|
constructor(builder: VertexBuilder) : this() {
|
||||||
load(builder, GL45.GL_STATIC_DRAW)
|
load(builder, GL45.GL_STATIC_DRAW)
|
||||||
@ -44,6 +47,9 @@ class Mesh() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mesh + RenderConfig
|
||||||
|
*/
|
||||||
data class ConfiguredMesh<T : GLShaderProgram>(val config: RenderConfig<T>, val mesh: Mesh = Mesh()) {
|
data class ConfiguredMesh<T : GLShaderProgram>(val config: RenderConfig<T>, val mesh: Mesh = Mesh()) {
|
||||||
fun render() {
|
fun render() {
|
||||||
config.setup()
|
config.setup()
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.render
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
|
||||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction
|
|
||||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
|
||||||
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
|
|
||||||
import java.util.stream.Stream
|
|
||||||
|
|
||||||
class MultiMeshBuilder {
|
|
||||||
data class Entry(val config: RenderConfig<*>, val builder: VertexBuilder, val layer: Long)
|
|
||||||
|
|
||||||
private val meshes = Reference2ObjectOpenHashMap<RenderConfig<*>, Long2ObjectOpenHashMap<Entry>>()
|
|
||||||
|
|
||||||
fun get(config: RenderConfig<*>, layer: Long): VertexBuilder {
|
|
||||||
return meshes.computeIfAbsent(config, Reference2ObjectFunction {
|
|
||||||
Long2ObjectOpenHashMap()
|
|
||||||
}).computeIfAbsent(layer, Long2ObjectFunction { Entry(config, VertexBuilder(config.program.attributes, config.initialBuilderCapacity), layer) }).builder
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clear() = meshes.clear()
|
|
||||||
fun isEmpty() = meshes.isEmpty()
|
|
||||||
fun isNotEmpty() = meshes.isNotEmpty()
|
|
||||||
|
|
||||||
fun meshes(): Stream<Entry> {
|
|
||||||
return meshes.values.stream().flatMap { it.values.stream() }
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,7 +5,7 @@ import ru.dbotthepony.kvector.arrays.Matrix4f
|
|||||||
|
|
||||||
abstract class RenderConfig<out T : GLShaderProgram>(val program: T) {
|
abstract class RenderConfig<out T : GLShaderProgram>(val program: T) {
|
||||||
val client get() = program.client
|
val client get() = program.client
|
||||||
open val initialBuilderCapacity: Int get() = 64
|
open val initialBuilderCapacity: Int get() = 32
|
||||||
|
|
||||||
open fun setup() {
|
open fun setup() {
|
||||||
program.use()
|
program.use()
|
||||||
|
@ -1,81 +1,112 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.render
|
package ru.dbotthepony.kstarbound.client.render
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
|
|
||||||
const val RenderLayerUpperBits = 5
|
enum class RenderLayer {
|
||||||
const val RenderLayerLowerBits = 32 - RenderLayerUpperBits
|
BackgroundOverlay,
|
||||||
const val RenderLayerLowerMask = 0.inv() shr RenderLayerUpperBits
|
BackgroundTile,
|
||||||
|
BackgroundTileMod,
|
||||||
|
Platform,
|
||||||
|
Plant,
|
||||||
|
PlantDrop,
|
||||||
|
Object,
|
||||||
|
PreviewObject,
|
||||||
|
BackParticle,
|
||||||
|
Vehicle,
|
||||||
|
Effect,
|
||||||
|
Projectile,
|
||||||
|
Monster,
|
||||||
|
Npc,
|
||||||
|
Player,
|
||||||
|
ItemDrop,
|
||||||
|
Liquid,
|
||||||
|
MiddleParticle,
|
||||||
|
ForegroundTile,
|
||||||
|
ForegroundTileMod,
|
||||||
|
ForegroundEntity,
|
||||||
|
ForegroundOverlay,
|
||||||
|
FrontParticle,
|
||||||
|
Overlay;
|
||||||
|
|
||||||
enum class RenderLayer(val index: Long) {
|
val base = Point(this)
|
||||||
BackgroundOverlay (1L shl RenderLayerLowerBits),
|
|
||||||
BackgroundTile (2L shl RenderLayerLowerBits),
|
fun point(offset: Long = 0L): Point {
|
||||||
Platform (3L shl RenderLayerLowerBits),
|
return if (offset == 0L)
|
||||||
Plant (4L shl RenderLayerLowerBits),
|
base
|
||||||
PlantDrop (5L shl RenderLayerLowerBits),
|
else
|
||||||
Object (6L shl RenderLayerLowerBits),
|
Point(this, offset)
|
||||||
PreviewObject (7L shl RenderLayerLowerBits),
|
}
|
||||||
BackParticle (8L shl RenderLayerLowerBits),
|
|
||||||
Vehicle (9L shl RenderLayerLowerBits),
|
data class Point(val base: RenderLayer, val offset: Long = 0L) : Comparable<Point> {
|
||||||
Effect (10L shl RenderLayerLowerBits),
|
override fun compareTo(other: Point): Int {
|
||||||
Projectile (11L shl RenderLayerLowerBits),
|
var cmp = base.compareTo(other.base)
|
||||||
Monster (12L shl RenderLayerLowerBits),
|
if (cmp == 0) cmp = offset.compareTo(other.offset)
|
||||||
Npc (13L shl RenderLayerLowerBits),
|
return cmp
|
||||||
Player (14L shl RenderLayerLowerBits),
|
}
|
||||||
ItemDrop (15L shl RenderLayerLowerBits),
|
}
|
||||||
Liquid (16L shl RenderLayerLowerBits),
|
|
||||||
MiddleParticle (17L shl RenderLayerLowerBits),
|
|
||||||
ForegroundTile (18L shl RenderLayerLowerBits),
|
|
||||||
ForegroundEntity (19L shl RenderLayerLowerBits),
|
|
||||||
ForegroundOverlay (20L shl RenderLayerLowerBits),
|
|
||||||
FrontParticle (21L shl RenderLayerLowerBits),
|
|
||||||
Overlay (22L shl RenderLayerLowerBits);
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = LogManager.getLogger()
|
fun tileLayer(isBackground: Boolean, isModifier: Boolean, offset: Long = 0L): Point {
|
||||||
|
if (isBackground && isModifier) {
|
||||||
private inline fun perform(value: String, symbol: Char, operator: (Long, Long) -> Long): Long {
|
return BackgroundTileMod.point(offset)
|
||||||
val split = value.split(symbol)
|
} else if (isBackground) {
|
||||||
|
return BackgroundTile.point(offset)
|
||||||
if (split.size != 2) {
|
} else if (isModifier) {
|
||||||
logger.error("Ambiguous render layer string: $value; assuming 0")
|
return ForegroundTileMod.point(offset)
|
||||||
return 0
|
|
||||||
} else {
|
} else {
|
||||||
val enum = entries.find { it.name == split[0] }
|
return ForegroundTile.point(offset)
|
||||||
|
|
||||||
if (enum == null) {
|
|
||||||
logger.error("Unknown render layer: ${split[0]} in $value; assuming 0")
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
val num = split[1].toLongOrNull()
|
|
||||||
|
|
||||||
if (num == null) {
|
|
||||||
logger.error("Invalid render layer string: $value; assuming 0")
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return operator(enum.index, num)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parse(value: String): Long {
|
private val logger = LogManager.getLogger()
|
||||||
|
private val lowercase: ImmutableMap<String, RenderLayer>
|
||||||
|
|
||||||
|
init {
|
||||||
|
val builder = ImmutableMap.Builder<String, RenderLayer>()
|
||||||
|
|
||||||
|
for (value in entries) {
|
||||||
|
builder.put(value.name.lowercase(), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
lowercase = builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun perform(value: String, symbol: Char): Point {
|
||||||
|
val before = value.substringBefore(symbol).lowercase()
|
||||||
|
val after = value.substring(before.length)
|
||||||
|
|
||||||
|
val enum = lowercase[before]
|
||||||
|
|
||||||
|
if (enum == null) {
|
||||||
|
logger.error("Unknown render layer: $before in $value; assuming BackgroundOverlay")
|
||||||
|
return BackgroundOverlay.base
|
||||||
|
}
|
||||||
|
|
||||||
|
val num = after.toLongOrNull()
|
||||||
|
|
||||||
|
if (num == null) {
|
||||||
|
logger.error("Invalid render layer string: $value; assuming BackgroundOverlay")
|
||||||
|
return BackgroundOverlay.base
|
||||||
|
}
|
||||||
|
|
||||||
|
return enum.point(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parse(value: String): Point {
|
||||||
if ('+' in value) {
|
if ('+' in value) {
|
||||||
return perform(value, '+', Long::plus)
|
return perform(value, '+')
|
||||||
} else if ('-' in value) {
|
} else if ('-' in value) {
|
||||||
return perform(value, '-', Long::minus)
|
return perform(value, '-')
|
||||||
} else if ('*' in value) {
|
|
||||||
return perform(value, '*', Long::times)
|
|
||||||
} else if ('/' in value) {
|
|
||||||
return perform(value, '/', Long::div)
|
|
||||||
} else {
|
} else {
|
||||||
val enum = entries.find { it.name == value }
|
val enum = lowercase[value.lowercase()]
|
||||||
|
|
||||||
if (enum == null) {
|
if (enum == null) {
|
||||||
logger.error("Unknown render layer: $value; assuming 0")
|
logger.error("Unknown render layer: $value; assuming BackgroundOverlay")
|
||||||
return 0
|
return BackgroundOverlay.base
|
||||||
}
|
}
|
||||||
|
|
||||||
return enum.index
|
return enum.base
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@ class TileRenderers(val client: StarboundClient) {
|
|||||||
private val background = HashMap<GLTexture2D, Config>()
|
private val background = HashMap<GLTexture2D, Config>()
|
||||||
private val matCache = HashMap<String, TileRenderer>()
|
private val matCache = HashMap<String, TileRenderer>()
|
||||||
private val modCache = HashMap<String, TileRenderer>()
|
private val modCache = HashMap<String, TileRenderer>()
|
||||||
private val identity = Matrix3f.identity()
|
|
||||||
|
|
||||||
fun getMaterialRenderer(defName: String): TileRenderer {
|
fun getMaterialRenderer(defName: String): TileRenderer {
|
||||||
return matCache.computeIfAbsent(defName) {
|
return matCache.computeIfAbsent(defName) {
|
||||||
@ -49,15 +48,13 @@ class TileRenderers(val client: StarboundClient) {
|
|||||||
|
|
||||||
override fun setup() {
|
override fun setup() {
|
||||||
super.setup()
|
super.setup()
|
||||||
client.activeTexture = 0
|
|
||||||
client.depthTest = false
|
client.depthTest = false
|
||||||
program.texture0 = 0
|
program.texture0 = 0
|
||||||
texture.bind()
|
client.textures2D[0] = texture
|
||||||
texture.textureMagFilter = GL_NEAREST
|
texture.textureMagFilter = GL_NEAREST
|
||||||
texture.textureMinFilter = GL_NEAREST
|
texture.textureMinFilter = GL_NEAREST
|
||||||
|
|
||||||
program.worldMatrix = client.stack.last()
|
program.modelMatrix = client.stack.last()
|
||||||
program.modelMatrix = identity
|
|
||||||
program.colorMultiplier = color
|
program.colorMultiplier = color
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,8 +94,8 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
|
|||||||
HALT
|
HALT
|
||||||
}
|
}
|
||||||
|
|
||||||
val state get() = renderers.client
|
val client get() = renderers.client
|
||||||
val texture = def.renderParameters.texture?.imagePath?.value?.let { state.loadTexture(it).also { it.textureMagFilter = GL_NEAREST }}
|
val texture = def.renderParameters.texture?.imagePath?.value?.let { client.loadTexture(it).also { it.textureMagFilter = GL_NEAREST }}
|
||||||
|
|
||||||
val equalityTester: EqualityRuleTester = when (def) {
|
val equalityTester: EqualityRuleTester = when (def) {
|
||||||
is TileDefinition -> TileEqualityTester(def)
|
is TileDefinition -> TileEqualityTester(def)
|
||||||
@ -161,29 +158,32 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
|
|||||||
self: ITileState,
|
self: ITileState,
|
||||||
matchPiece: RenderMatch,
|
matchPiece: RenderMatch,
|
||||||
getter: ITileAccess,
|
getter: ITileAccess,
|
||||||
meshBuilder: MultiMeshBuilder,
|
meshBuilder: LayeredRenderer,
|
||||||
pos: Vector2i,
|
pos: Vector2i,
|
||||||
thisBuilder: VertexBuilder,
|
thisBuilder: VertexBuilder,
|
||||||
background: Boolean,
|
isBackground: Boolean,
|
||||||
isModifier: Boolean,
|
isModifier: Boolean,
|
||||||
): TestResult {
|
): TestResult {
|
||||||
if (matchPiece.test(getter, equalityTester, pos)) {
|
if (matchPiece.test(getter, equalityTester, pos)) {
|
||||||
for (renderPiece in matchPiece.pieces) {
|
for (renderPiece in matchPiece.pieces) {
|
||||||
if (renderPiece.piece.texture != null) {
|
if (renderPiece.piece.texture != null) {
|
||||||
val program = if (background) {
|
val program = if (isBackground) {
|
||||||
renderers.background(state.loadTexture(renderPiece.piece.texture!!))
|
renderers.background(client.loadTexture(renderPiece.piece.texture!!))
|
||||||
} else {
|
} else {
|
||||||
renderers.foreground(state.loadTexture(renderPiece.piece.texture!!))
|
renderers.foreground(client.loadTexture(renderPiece.piece.texture!!))
|
||||||
}
|
}
|
||||||
|
|
||||||
tesselateAt(self, renderPiece.piece, getter, meshBuilder.get(program, def.renderParameters.zLevel).mode(GeometryType.QUADS), pos, renderPiece.offset, isModifier)
|
tesselateAt(
|
||||||
|
self, renderPiece.piece, getter,
|
||||||
|
meshBuilder.getBuilder(RenderLayer.tileLayer(isBackground, isModifier, def.renderParameters.zLevel), program).mode(GeometryType.QUADS),
|
||||||
|
pos, renderPiece.offset, isModifier)
|
||||||
} else {
|
} else {
|
||||||
tesselateAt(self, renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset, isModifier)
|
tesselateAt(self, renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset, isModifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (subPiece in matchPiece.subMatches) {
|
for (subPiece in matchPiece.subMatches) {
|
||||||
val matched = tesselatePiece(self, subPiece, getter, meshBuilder, pos, thisBuilder, background, isModifier)
|
val matched = tesselatePiece(self, subPiece, getter, meshBuilder, pos, thisBuilder, isBackground, isModifier)
|
||||||
|
|
||||||
if (matched == TestResult.HALT || matched == TestResult.CONTINUE && matchPiece.haltOnSubMatch) {
|
if (matched == TestResult.HALT || matched == TestResult.CONTINUE && matchPiece.haltOnSubMatch) {
|
||||||
return TestResult.HALT
|
return TestResult.HALT
|
||||||
@ -209,18 +209,20 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
|
|||||||
*
|
*
|
||||||
* Тесселирует тайлы в нужный VertexBuilder с масштабом согласно константе [PIXELS_IN_STARBOUND_UNITf]
|
* Тесселирует тайлы в нужный VertexBuilder с масштабом согласно константе [PIXELS_IN_STARBOUND_UNITf]
|
||||||
*/
|
*/
|
||||||
fun tesselate(self: ITileState, getter: ITileAccess, meshBuilder: MultiMeshBuilder, pos: Vector2i, background: Boolean = false, isModifier: Boolean = false) {
|
fun tesselate(self: ITileState, getter: ITileAccess, meshBuilder: LayeredRenderer, pos: Vector2i, isBackground: Boolean = false, isModifier: Boolean = false) {
|
||||||
if (texture == null) return
|
if (texture == null) return
|
||||||
|
|
||||||
// если у нас нет renderTemplate
|
// если у нас нет renderTemplate
|
||||||
// то мы просто не можем его отрисовать
|
// то мы просто не можем его отрисовать
|
||||||
val template = def.renderTemplate.value ?: return
|
val template = def.renderTemplate.value ?: return
|
||||||
|
|
||||||
val vertexBuilder = meshBuilder.get(if (background) bakedBackgroundProgramState!! else bakedProgramState!!, def.renderParameters.zLevel).mode(GeometryType.QUADS)
|
val vertexBuilder = meshBuilder
|
||||||
|
.getBuilder(RenderLayer.tileLayer(isBackground, isModifier, def.renderParameters.zLevel), if (isBackground) bakedBackgroundProgramState!! else bakedProgramState!!)
|
||||||
|
.mode(GeometryType.QUADS)
|
||||||
|
|
||||||
for ((_, matcher) in template.matches) {
|
for ((_, matcher) in template.matches) {
|
||||||
for (matchPiece in matcher) {
|
for (matchPiece in matcher) {
|
||||||
val matched = tesselatePiece(self, matchPiece, getter, meshBuilder, pos, vertexBuilder, background, isModifier)
|
val matched = tesselatePiece(self, matchPiece, getter, meshBuilder, pos, vertexBuilder, isBackground, isModifier)
|
||||||
|
|
||||||
if (matched == TestResult.HALT) {
|
if (matched == TestResult.HALT) {
|
||||||
break
|
break
|
||||||
|
@ -10,7 +10,6 @@ import ru.dbotthepony.kstarbound.client.StarboundClient
|
|||||||
import ru.dbotthepony.kstarbound.client.render.ConfiguredMesh
|
import ru.dbotthepony.kstarbound.client.render.ConfiguredMesh
|
||||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||||
import ru.dbotthepony.kstarbound.client.render.Mesh
|
import ru.dbotthepony.kstarbound.client.render.Mesh
|
||||||
import ru.dbotthepony.kstarbound.client.render.MultiMeshBuilder
|
|
||||||
import ru.dbotthepony.kstarbound.client.render.RenderLayer
|
import ru.dbotthepony.kstarbound.client.render.RenderLayer
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||||
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
||||||
@ -72,7 +71,7 @@ class ClientWorld(
|
|||||||
|
|
||||||
inner class RenderRegion(val x: Int, val y: Int) {
|
inner class RenderRegion(val x: Int, val y: Int) {
|
||||||
inner class Layer(private val view: ITileAccess, private val isBackground: Boolean) {
|
inner class Layer(private val view: ITileAccess, private val isBackground: Boolean) {
|
||||||
val bakedMeshes = ArrayList<Pair<ConfiguredMesh<*>, Long>>()
|
val bakedMeshes = ArrayList<Pair<ConfiguredMesh<*>, RenderLayer.Point>>()
|
||||||
var isDirty = true
|
var isDirty = true
|
||||||
|
|
||||||
fun bake() {
|
fun bake() {
|
||||||
@ -81,7 +80,7 @@ class ClientWorld(
|
|||||||
|
|
||||||
bakedMeshes.clear()
|
bakedMeshes.clear()
|
||||||
|
|
||||||
val meshes = MultiMeshBuilder()
|
val meshes = LayeredRenderer()
|
||||||
|
|
||||||
for (x in 0 until renderRegionWidth) {
|
for (x in 0 until renderRegionWidth) {
|
||||||
for (y in 0 until renderRegionHeight) {
|
for (y in 0 until renderRegionHeight) {
|
||||||
@ -91,19 +90,23 @@ class ClientWorld(
|
|||||||
val material = tile.material
|
val material = tile.material
|
||||||
|
|
||||||
if (!material.isMeta) {
|
if (!material.isMeta) {
|
||||||
client.tileRenderers.getMaterialRenderer(material.materialName).tesselate(tile, view, meshes, Vector2i(x, y), background = isBackground)
|
client.tileRenderers
|
||||||
|
.getMaterialRenderer(material.materialName)
|
||||||
|
.tesselate(tile, view, meshes, Vector2i(x, y), isBackground = isBackground)
|
||||||
}
|
}
|
||||||
|
|
||||||
val modifier = tile.modifier
|
val modifier = tile.modifier
|
||||||
|
|
||||||
if (modifier != null) {
|
if (modifier != null) {
|
||||||
client.tileRenderers.getModifierRenderer(modifier.modName).tesselate(tile, view, meshes, Vector2i(x, y), background = isBackground, isModifier = true)
|
client.tileRenderers
|
||||||
|
.getModifierRenderer(modifier.modName)
|
||||||
|
.tesselate(tile, view, meshes, Vector2i(x, y), isBackground = isBackground, isModifier = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for ((baked, builder, zLevel) in meshes.meshes()) {
|
for ((baked, zLevel) in meshes.bakeIntoMeshes()) {
|
||||||
bakedMeshes.add(ConfiguredMesh(baked, Mesh(builder)) to zLevel)
|
bakedMeshes.add(baked to zLevel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,7 +158,7 @@ class ClientWorld(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for ((baked, zLevel) in background.bakedMeshes) {
|
for ((baked, zLevel) in background.bakedMeshes) {
|
||||||
layers.add(zLevel + RenderLayer.BackgroundTile.index) {
|
layers.add(zLevel) {
|
||||||
client.stack.push().last().translate(renderOrigin.x, renderOrigin.y)
|
client.stack.push().last().translate(renderOrigin.x, renderOrigin.y)
|
||||||
baked.render()
|
baked.render()
|
||||||
client.stack.pop()
|
client.stack.pop()
|
||||||
@ -163,7 +166,7 @@ class ClientWorld(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for ((baked, zLevel) in foreground.bakedMeshes) {
|
for ((baked, zLevel) in foreground.bakedMeshes) {
|
||||||
layers.add(zLevel + RenderLayer.ForegroundTile.index) {
|
layers.add(zLevel) {
|
||||||
client.stack.push().last().translate(renderOrigin.x, renderOrigin.y)
|
client.stack.push().last().translate(renderOrigin.x, renderOrigin.y)
|
||||||
baked.render()
|
baked.render()
|
||||||
client.stack.pop()
|
client.stack.pop()
|
||||||
@ -268,23 +271,11 @@ class ClientWorld(
|
|||||||
|
|
||||||
for (obj in objects) {
|
for (obj in objects) {
|
||||||
if (obj.pos.x in client.viewportCellX .. client.viewportCellX + client.viewportCellWidth && obj.pos.y in client.viewportCellY .. client.viewportCellY + client.viewportCellHeight) {
|
if (obj.pos.x in client.viewportCellX .. client.viewportCellX + client.viewportCellWidth && obj.pos.y in client.viewportCellY .. client.viewportCellY + client.viewportCellHeight) {
|
||||||
//layers.add(RenderLayer.Object.index) {
|
val layer = layers.getLayer(obj.orientation?.renderLayer ?: continue)
|
||||||
layers.add(obj.orientation?.renderLayer ?: continue) {
|
|
||||||
/*client.quadWireframe {
|
|
||||||
it.vertex(obj.pos.x.toFloat(), obj.pos.y.toFloat())
|
|
||||||
it.vertex(obj.pos.x.toFloat() + 1f, obj.pos.y.toFloat())
|
|
||||||
it.vertex(obj.pos.x.toFloat() + 1f, obj.pos.y.toFloat() + 1f)
|
|
||||||
it.vertex(obj.pos.x.toFloat(), obj.pos.y.toFloat() + 1f)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
obj.drawables.forEach {
|
obj.drawables.forEach {
|
||||||
val (x, y) = obj.imagePosition
|
val (x, y) = obj.imagePosition
|
||||||
|
it.render(client, layer, obj.pos.x.toFloat() + x / PIXELS_IN_STARBOUND_UNITf, obj.pos.y.toFloat() + y / PIXELS_IN_STARBOUND_UNITf)
|
||||||
client.stack.push().last()
|
|
||||||
.translate(obj.pos.x.toFloat() + x / PIXELS_IN_STARBOUND_UNITf, obj.pos.y.toFloat() + y / PIXELS_IN_STARBOUND_UNITf)
|
|
||||||
it.render(client)
|
|
||||||
client.stack.pop()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.addLights(client.viewportLighting, client.viewportCellX, client.viewportCellY)
|
obj.addLights(client.viewportLighting, client.viewportCellX, client.viewportCellY)
|
||||||
|
@ -11,6 +11,7 @@ import org.apache.logging.log4j.LogManager
|
|||||||
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
|
import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
|
||||||
|
import ru.dbotthepony.kstarbound.client.render.IGeometryLayer
|
||||||
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
||||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
||||||
import ru.dbotthepony.kstarbound.io.json.consumeNull
|
import ru.dbotthepony.kstarbound.io.json.consumeNull
|
||||||
@ -34,7 +35,7 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
|
|||||||
color: RGBAColor = RGBAColor.WHITE,
|
color: RGBAColor = RGBAColor.WHITE,
|
||||||
fullbright: Boolean = false
|
fullbright: Boolean = false
|
||||||
) : Drawable(position, color, fullbright) {
|
) : Drawable(position, color, fullbright) {
|
||||||
override fun render(client: StarboundClient, x: Float, y: Float) {
|
override fun render(client: StarboundClient, layer: IGeometryLayer, x: Float, y: Float) {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
|
|||||||
color: RGBAColor = RGBAColor.WHITE,
|
color: RGBAColor = RGBAColor.WHITE,
|
||||||
fullbright: Boolean = false
|
fullbright: Boolean = false
|
||||||
) : Drawable(position, color, fullbright) {
|
) : Drawable(position, color, fullbright) {
|
||||||
override fun render(client: StarboundClient, x: Float, y: Float) {
|
override fun render(client: StarboundClient, layer: IGeometryLayer, x: Float, y: Float) {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,12 +80,12 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
|
|||||||
return Image(path, transform.flatMap({ it.copy().scale(-1f, 1f) }, { it.copy(mirrored = !it.mirrored) }), position, color, fullbright)
|
return Image(path, transform.flatMap({ it.copy().scale(-1f, 1f) }, { it.copy(mirrored = !it.mirrored) }), position, color, fullbright)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun render(client: StarboundClient, x: Float, y: Float) {
|
override fun render(client: StarboundClient, layer: IGeometryLayer, x: Float, y: Float) {
|
||||||
val sprite = path.sprite ?: return
|
val sprite = path.sprite ?: return
|
||||||
val texture = client.loadTexture(path.imagePath.value!!)
|
val texture = client.loadTexture(path.imagePath.value!!)
|
||||||
val program = if (fullbright) client.programs.positionTexture else client.programs.positionTextureLightmap
|
val program = if (fullbright) client.programs.positionTexture else client.programs.positionTextureLightmap
|
||||||
|
|
||||||
val mat = transform.map({ it }, {
|
val mat = transform.map({ it.copy() }, {
|
||||||
val mat = Matrix3f.identity()
|
val mat = Matrix3f.identity()
|
||||||
|
|
||||||
it.scale.map({ mat.scale(it / PIXELS_IN_STARBOUND_UNITf, it / PIXELS_IN_STARBOUND_UNITf) }, { mat.scale(it / PIXELS_IN_STARBOUND_UNITf) })
|
it.scale.map({ mat.scale(it / PIXELS_IN_STARBOUND_UNITf, it / PIXELS_IN_STARBOUND_UNITf) }, { mat.scale(it / PIXELS_IN_STARBOUND_UNITf) })
|
||||||
@ -105,27 +106,21 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
|
|||||||
mat
|
mat
|
||||||
})
|
})
|
||||||
|
|
||||||
//program.modelMatrix = mat
|
val builder = layer.getBuilder(program.config(texture))
|
||||||
program.modelMatrix = Matrix3f.identity()
|
|
||||||
|
|
||||||
program.worldMatrix = client.stack.last()
|
mat.preTranslate(x, y)
|
||||||
program.use()
|
client.stack.last().mulIntoOther(mat)
|
||||||
program.texture0 = 0
|
|
||||||
client.activeTexture = 0
|
|
||||||
texture.bind()
|
|
||||||
|
|
||||||
program.builder.builder.begin(GeometryType.QUADS)
|
builder.mode(GeometryType.QUADS)
|
||||||
program.builder.builder.vertex(mat, x, y).uv(sprite.u0, sprite.v0)
|
builder.vertex(mat, 0f, 0f).uv(sprite.u0, sprite.v0)
|
||||||
program.builder.builder.vertex(mat, x + sprite.width, y).uv(sprite.u1, sprite.v0)
|
builder.vertex(mat, 0f + sprite.width, 0f).uv(sprite.u1, sprite.v0)
|
||||||
program.builder.builder.vertex(mat, x + sprite.width, y + sprite.height).uv(sprite.u1, sprite.v1)
|
builder.vertex(mat, 0f + sprite.width, 0f + sprite.height).uv(sprite.u1, sprite.v1)
|
||||||
program.builder.builder.vertex(mat, x, y + sprite.height).uv(sprite.u0, sprite.v1)
|
builder.vertex(mat, 0f, 0f + sprite.height).uv(sprite.u0, sprite.v1)
|
||||||
program.builder.upload()
|
|
||||||
program.builder.draw()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Empty(position: Vector2f = Vector2f.ZERO, color: RGBAColor = RGBAColor.WHITE, fullbright: Boolean = false) : Drawable(position, color, fullbright) {
|
class Empty(position: Vector2f = Vector2f.ZERO, color: RGBAColor = RGBAColor.WHITE, fullbright: Boolean = false) : Drawable(position, color, fullbright) {
|
||||||
override fun render(client: StarboundClient, x: Float, y: Float) {}
|
override fun render(client: StarboundClient, layer: IGeometryLayer, x: Float, y: Float) {}
|
||||||
|
|
||||||
override fun flop(): Drawable {
|
override fun flop(): Drawable {
|
||||||
return this
|
return this
|
||||||
@ -136,7 +131,7 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun render(client: StarboundClient = StarboundClient.current(), x: Float = 0f, y: Float = 0f)
|
abstract fun render(client: StarboundClient = StarboundClient.current(), layer: IGeometryLayer, x: Float = 0f, y: Float = 0f)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mirror along X axis
|
* mirror along X axis
|
||||||
|
@ -36,7 +36,7 @@ data class ObjectOrientation(
|
|||||||
val json: JsonObject,
|
val json: JsonObject,
|
||||||
val flipImages: Boolean = false,
|
val flipImages: Boolean = false,
|
||||||
val drawables: ImmutableList<Drawable>,
|
val drawables: ImmutableList<Drawable>,
|
||||||
val renderLayer: Long,
|
val renderLayer: RenderLayer.Point,
|
||||||
val imagePosition: Vector2f,
|
val imagePosition: Vector2f,
|
||||||
val frames: Int,
|
val frames: Int,
|
||||||
val animationCycle: Double,
|
val animationCycle: Double,
|
||||||
|
@ -476,6 +476,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
clusters.removeIf {
|
clusters.removeIf {
|
||||||
val grid = it.grid
|
val grid = it.grid
|
||||||
|
|
||||||
|
// wait until thread local caches are refreshed
|
||||||
if (grid != null) {
|
if (grid != null) {
|
||||||
if (targetMem != null) {
|
if (targetMem != null) {
|
||||||
for (x in grid.minX - 1 .. grid.maxX) {
|
for (x in grid.minX - 1 .. grid.maxX) {
|
||||||
|
@ -3,7 +3,7 @@ uniform vec4 colorMultiplier;
|
|||||||
out vec4 colorResult;
|
out vec4 colorResult;
|
||||||
|
|
||||||
#ifdef TEXTURE
|
#ifdef TEXTURE
|
||||||
uniform sampler2D texture0;
|
layout (binding = 0) uniform sampler2D texture0;
|
||||||
in vec2 uvOut;
|
in vec2 uvOut;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -17,11 +17,10 @@ out float hueShiftOut;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
uniform mat3 viewMatrix; // projection (viewport) + camera
|
uniform mat3 viewMatrix; // projection (viewport) + camera
|
||||||
uniform mat3 worldMatrix; // matrix stack
|
|
||||||
uniform mat3 modelMatrix; // local transformations
|
uniform mat3 modelMatrix; // local transformations
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 result = viewMatrix * worldMatrix * modelMatrix * pos;
|
vec3 result = viewMatrix * modelMatrix * pos;
|
||||||
gl_Position = vec4(result.x, result.y, 0.0, result.z);
|
gl_Position = vec4(result.x, result.y, 0.0, result.z);
|
||||||
|
|
||||||
#ifdef HUE_SHIFT
|
#ifdef HUE_SHIFT
|
||||||
|
@ -5,12 +5,12 @@ layout (location = 0) in vec3 pos;
|
|||||||
layout (location = 1) in vec2 uvData;
|
layout (location = 1) in vec2 uvData;
|
||||||
|
|
||||||
uniform mat3 viewMatrix; // projection (viewport)
|
uniform mat3 viewMatrix; // projection (viewport)
|
||||||
uniform mat3 worldMatrix; // world position
|
uniform mat3 modelMatrix; // world position
|
||||||
|
|
||||||
out vec2 uvOut;
|
out vec2 uvOut;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 result = viewMatrix * worldMatrix * pos;
|
vec3 result = viewMatrix * modelMatrix * pos;
|
||||||
gl_Position = vec4(result.x, result.y, 0.0, result.z);
|
gl_Position = vec4(result.x, result.y, 0.0, result.z);
|
||||||
|
|
||||||
uvOut = uvData;
|
uvOut = uvData;
|
||||||
|
Loading…
Reference in New Issue
Block a user