Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions worldwind/src/commonMain/kotlin/earth/worldwind/draw/DrawQuadState.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package earth.worldwind.draw

import earth.worldwind.geom.Matrix3
import earth.worldwind.geom.Matrix4
import earth.worldwind.geom.Vec2
import earth.worldwind.geom.Vec3
import earth.worldwind.render.Color
import earth.worldwind.render.Texture
import earth.worldwind.render.buffer.BufferObject
import earth.worldwind.render.program.AbstractShaderProgram

open class DrawQuadState internal constructor() {
companion object {
const val MAX_DRAW_ELEMENTS = 5
}

var programDrawToTexture: AbstractShaderProgram? = null
var programDrawTextureToTerrain: AbstractShaderProgram? = null
var vertexBuffer: BufferObject? = null
var elementBuffer: BufferObject? = null
val vertexOrigin = Vec3()

var A = Vec2()
var B = Vec2()
var C = Vec2()
var D = Vec2()
var vertexStride = 0
var enableCullFace = true
var enableDepthTest = true
var enableLighting = false
var depthOffset = 0.0
val color = Color()
var opacity = 1.0f
var texture: Texture? = null
var textureLod = 0
val texCoordMatrix = Matrix3()
val texCoordAttrib = VertexAttrib()
internal var primCount = 0
internal val prims = Array(MAX_DRAW_ELEMENTS) { DrawElements() }

open fun reset() {
programDrawToTexture = null
programDrawTextureToTerrain = null
vertexBuffer = null
elementBuffer = null
vertexOrigin.set(0.0, 0.0, 0.0)
vertexStride = 0
enableCullFace = true
enableDepthTest = true
enableLighting = false
depthOffset = 0.0
color.set(1f, 1f, 1f, 1f)
opacity = 1.0f
texture = null
textureLod = 0
texCoordMatrix.setToIdentity()
texCoordAttrib.size = 0
texCoordAttrib.offset = 0
primCount = 0
A.set(0.0, 0.0)
B.set(0.0, 0.0)
C.set(0.0, 0.0)
D.set(0.0, 0.0)

for (idx in 0 until MAX_DRAW_ELEMENTS) prims[idx].texture = null
}

open fun drawElements(mode: Int, count: Int, type: Int, offset: Int) {
val prim = prims[primCount++]
prim.mode = mode
prim.count = count
prim.type = type
prim.offset = offset
prim.color.copy(color)
prim.opacity = opacity
prim.depthOffset = depthOffset
prim.texture = texture
prim.textureLod = textureLod
prim.texCoordMatrix.copy(texCoordMatrix)
prim.texCoordAttrib.copy(texCoordAttrib)
prim.A.copy(A)
prim.B.copy(B)
prim.C.copy(C)
prim.D.copy(D)
}

internal open class DrawElements {
var mode = 0
var count = 0
var type = 0
var offset = 0
val color = Color()
var opacity = 1.0f
var depthOffset = 0.0
var texture: Texture? = null
var textureLod = 0
val texCoordMatrix = Matrix3()
val texCoordAttrib = VertexAttrib()

var A = Vec2()
var B = Vec2()
var C = Vec2()
var D = Vec2()
}

open class VertexAttrib {
var size = 0
var offset = 0

fun copy(attrib: VertexAttrib) {
size = attrib.size
offset = attrib.offset
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
package earth.worldwind.draw

import earth.worldwind.geom.Matrix3
import earth.worldwind.geom.Matrix4
import earth.worldwind.geom.Sector
import earth.worldwind.geom.Vec2
import earth.worldwind.globe.Globe
import earth.worldwind.render.Color
import earth.worldwind.render.Texture
import earth.worldwind.render.program.SurfaceQuadShaderProgram
import earth.worldwind.render.program.TriangleShaderProgram
import earth.worldwind.util.Pool
import earth.worldwind.util.kgl.*
import kotlin.jvm.JvmStatic
import kotlin.math.cos

open class DrawableSurfaceQuad protected constructor(): Drawable {
var offset = Globe.Offset.Center
val sector = Sector()
val drawState = DrawQuadState()
var version = 0
var isDynamic = false
private var hash = 0
private var pool: Pool<DrawableSurfaceQuad>? = null
private val mvpMatrix = Matrix4()
private val textureMvpMatrix = Matrix4()

companion object {
val KEY = DrawableSurfaceQuad::class
private val identityMatrix3 = Matrix3()
private val color = Color()
private var opacity = 1.0f

@JvmStatic
fun obtain(pool: Pool<DrawableSurfaceQuad>): DrawableSurfaceQuad {
val instance = pool.acquire() ?: DrawableSurfaceQuad()
instance.pool = pool
return instance
}
}

override fun recycle() {
drawState.reset()
pool?.release(this)
pool = null
hash = 0
}

override fun draw(dc: DrawContext) {
// Make multi-texture unit 0 active.
dc.activeTextureUnit(GL_TEXTURE0)

// Set up to use vertex tex coord attributes.
dc.gl.enableVertexAttribArray(1 /*vertexTexCoord*/)
dc.gl.enableVertexAttribArray(2 /*vertexTexCoord*/)
dc.gl.enableVertexAttribArray(3 /*vertexTexCoord*/)

// Accumulate shapes in the draw context's scratch list.
// TODO accumulate in a geospatial quadtree
val scratchList = dc.scratchList
try {
// Add this shape.
scratchList.add(this)

// Add all shapes that are contiguous in the drawable queue.
while (true) {
val next = dc.peekDrawable() ?: break
// check if the drawable at the front of the queue can be batched
if (next !is DrawableSurfaceQuad || next.isDynamic != isDynamic) break
dc.pollDrawable() // take it off the queue
scratchList.add(next)
}

// Draw the accumulated shapes on each drawable terrain.
for (idx in 0 until dc.drawableTerrainCount) {
// Get the drawable terrain associated with the draw context.
val terrain = dc.getDrawableTerrain(idx)
// Draw the accumulated surface shapes to a texture representing the terrain's sector.
drawShapesToTexture(dc, terrain)?.let { texture ->
// Draw the texture containing the rasterized shapes onto the terrain geometry.
drawTextureToTerrain(dc, terrain, texture)
}
}
} finally {
// Clear the accumulated shapes.
scratchList.clear()
// Restore the default WorldWind OpenGL state.
dc.gl.disableVertexAttribArray(1 /*vertexTexCoord*/)
dc.gl.disableVertexAttribArray(2 /*vertexTexCoord*/)
dc.gl.disableVertexAttribArray(3 /*vertexTexCoord*/)
}
}

protected open fun textureHash(): Int {
if (hash == 0) {
hash = 31 * version
for (i in 0 until drawState.primCount) {
val prim = drawState.prims[i]
hash = 31 * hash + prim.color.hashCode()
hash = 31 * hash + prim.opacity.hashCode()
hash = 31 * hash + prim.texture.hashCode()
hash = 31 * hash + prim.textureLod
}
}
return hash
}

protected open fun drawShapesToTexture(dc: DrawContext, terrain: DrawableTerrain): Texture? {
// The terrain's sector defines the geographic region in which to draw.
val terrainSector = terrain.sector

// Filter shapes that intersect current terrain tile and globe offset
val scratchList = mutableListOf<DrawableSurfaceQuad>()
for (idx in dc.scratchList.indices) {
val shape = dc.scratchList[idx] as DrawableSurfaceQuad
if (shape.offset == terrain.offset && shape.sector.intersectsOrNextTo(terrainSector)) scratchList.add(shape)
}
if (scratchList.isEmpty()) return null // Nothing to draw

var hash = 0
val useCache = !dc.isPickMode && !isDynamic // Use single color attachment in pick mode and for dynamic shapes
if (useCache) {
// Calculate a texture cache key for this terrain tile and shapes batch
hash = terrainSector.hashCode()
for (idx in scratchList.indices) hash = 31 * hash + scratchList[idx].textureHash()

// Use cached texture
val cachedTexture = dc.texturesCache[hash]
if (cachedTexture != null) return cachedTexture
}

val program = drawState.programDrawToTexture as? SurfaceQuadShaderProgram ?: return null
if (!program.useProgram(dc)) return null

// Redraw shapes to texture and put in cache if required
val framebuffer = dc.scratchFramebuffer
val colorAttachment = framebuffer.getAttachedTexture(GL_COLOR_ATTACHMENT0)
val texture = if (!useCache) colorAttachment
else Texture(colorAttachment.width, colorAttachment.height, GL_RGBA, GL_UNSIGNED_BYTE, true)
try {
if (!framebuffer.bindFramebuffer(dc)) return null // framebuffer failed to bind
if (useCache) framebuffer.attachTexture(dc, texture, GL_COLOR_ATTACHMENT0)

// Clear the framebuffer and disable the depth test.
dc.gl.viewport(0, 0, colorAttachment.width, colorAttachment.height)
dc.gl.clear(GL_COLOR_BUFFER_BIT)
dc.gl.disable(GL_DEPTH_TEST)

// Use the draw context's pick mode.
program.enablePickMode(dc.isPickMode)

// Compute the tile common matrix that transforms geographic coordinates to texture fragments appropriate
// for the terrain sector.
// TODO capture this in a method on Matrix4
textureMvpMatrix.setToIdentity()
textureMvpMatrix.multiplyByTranslation(-1.0, -1.0, 0.0)
textureMvpMatrix.multiplyByScale(
2.0 / terrainSector.deltaLongitude.inDegrees,
2.0 / terrainSector.deltaLatitude.inDegrees,
0.0
)
textureMvpMatrix.multiplyByTranslation(
-terrainSector.minLongitude.inDegrees,
-terrainSector.minLatitude.inDegrees,
0.0
)
for (idx in scratchList.indices) {
val shape = scratchList[idx]
if (shape.drawState.vertexBuffer?.bindBuffer(dc) != true) continue // vertex buffer unspecified or failed to bind
if (shape.drawState.elementBuffer?.bindBuffer(dc) != true) continue // element buffer unspecified or failed to bind

// Transform local shape coordinates to texture fragments appropriate for the terrain sector.
mvpMatrix.copy(textureMvpMatrix)
mvpMatrix.multiplyByTranslation(
shape.drawState.vertexOrigin.x,
shape.drawState.vertexOrigin.y,
shape.drawState.vertexOrigin.z
)
program.loadModelviewProjection(mvpMatrix)

// Use the shape's vertex point attribute.
dc.gl.vertexAttribPointer(0 /*vertexPoint*/, 3, GL_FLOAT, false, shape.drawState.vertexStride, 0)

// Draw the specified primitives to the framebuffer texture.
for (primIdx in 0 until shape.drawState.primCount) {
val prim = shape.drawState.prims[primIdx]
program.loadColor(prim.color)
program.loadOpacity(prim.opacity)
if (prim.texture?.bindTexture(dc) == true) {
program.loadTexCoordMatrix(prim.texCoordMatrix)
program.loadABCD(prim.A, prim.B, prim.C, prim.D)
program.enableTexture(true)
} else {
program.enableTexture(false)
// prevent "RENDER WARNING: there is no texture bound to unit 0"
dc.defaultTexture.bindTexture(dc)
}

dc.gl.drawElements(prim.mode, prim.count, prim.type, prim.offset)
}
}
if (useCache) dc.texturesCache.put(hash, texture, 1)
} finally {
if (useCache) framebuffer.attachTexture(dc, colorAttachment, GL_COLOR_ATTACHMENT0)
// Restore the default WorldWind OpenGL state.
dc.bindFramebuffer(KglFramebuffer.NONE)
dc.gl.viewport(dc.viewport.x, dc.viewport.y, dc.viewport.width, dc.viewport.height)
dc.gl.enable(GL_DEPTH_TEST)
dc.gl.lineWidth(1f)
}
return texture
}

protected open fun drawTextureToTerrain(dc: DrawContext, terrain: DrawableTerrain, texture: Texture) {
val program = drawState.programDrawTextureToTerrain as? TriangleShaderProgram ?: return
if (!program.useProgram(dc)) return // program failed to build

try {
if (!terrain.useVertexPointAttrib(dc, 0 /*vertexPoint*/)) return // terrain vertex attribute failed to bind
if (!terrain.useVertexPointAttrib(dc, 1 /*vertexPoint*/)) return // terrain vertex attribute failed to bind
if (!terrain.useVertexPointAttrib(dc, 2 /*vertexPoint*/)) return // terrain vertex attribute failed to bind
if (!terrain.useVertexTexCoordAttrib(dc, 3 /*vertexTexCoord*/)) return // terrain vertex attribute failed to bind
if (!texture.bindTexture(dc)) return // texture failed to bind

// Configure the program to draw texture fragments unmodified and aligned with the terrain.
// TODO consolidate pickMode and enableTexture into a single textureMode
// TODO it's confusing that pickMode must be disabled during surface shape render-to-texture
program.enableOneVertexMode(true)
program.enablePickMode(false)
program.enableTexture(true)
program.loadTexCoordMatrix(identityMatrix3)
program.loadColor(color)
program.loadOpacity(opacity)
program.loadScreen(dc.viewport.width.toFloat(), dc.viewport.height.toFloat())

// Use the draw context's modelview projection matrix, transformed to terrain local coordinates.
val terrainOrigin = terrain.vertexOrigin
mvpMatrix.copy(dc.modelviewProjection)
mvpMatrix.multiplyByTranslation(terrainOrigin.x, terrainOrigin.y, terrainOrigin.z)
program.loadModelviewProjection(mvpMatrix)
program.loadClipDistance(0.0f)

// Draw the terrain as triangles.
terrain.drawTriangles(dc)
} finally {
// Unbind color attachment texture to avoid feedback loop
dc.defaultTexture.bindTexture(dc)
}
}
}
Loading
Loading