Skip to content
This repository has been archived by the owner on Jul 8, 2022. It is now read-only.

Initial GPU vector rendering as a sample #524

Merged
merged 25 commits into from
Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1ee213b
Add Dyn.getOrThrow
soywiz Mar 23, 2022
d1ce9fa
Add extra --add-opens
soywiz Mar 23, 2022
a6ec743
Expose pool for vertex buffers, vertex data and index buffers in the …
soywiz Mar 23, 2022
3af5e89
Initial code for replacing the OpenGL context
soywiz Mar 23, 2022
bbd87aa
Add buildVectorPath methods as aliases of buildPath
soywiz Mar 23, 2022
b515fac
Add stencil support to render buffers
soywiz Mar 23, 2022
02c6646
Add initial renderbufferStorageMultisample
soywiz Mar 23, 2022
c972d36
Add initial GpuShapeView (as a sample), that performs simple vector r…
soywiz Mar 23, 2022
1d8f949
Some work on further gpu fill shaders
soywiz Mar 24, 2022
a289068
AG improvements: scissor in clear + support setting depth & stencil i…
soywiz Mar 24, 2022
a652325
Fix CycleMethod for negative ratios
soywiz Mar 24, 2022
0f8859e
Optimize ShapeBuilder
soywiz Mar 24, 2022
c461b26
Added IDefaultShaders and VertexShaderDefault, FragmentShaderDefault …
soywiz Mar 24, 2022
0304122
Allow to convert TextShape into primitive shapes fill/stroke shapes
soywiz Mar 24, 2022
6bd59c5
Allow Sahpe.getBounds to reuse a BoundsBuilder
soywiz Mar 24, 2022
5e92be4
Added Ghostscript_Tiger.svg sample
soywiz Mar 24, 2022
4829885
More work on GpuShapeView
soywiz Mar 24, 2022
20fbc73
Huge performance improvement on Java Macos JNA by using Direct Mappin…
soywiz Mar 24, 2022
e49c97f
Simplify DirectGL
soywiz Mar 24, 2022
b8c5b60
Some improvements
soywiz Mar 24, 2022
c0cc110
Some performance improvements
soywiz Mar 24, 2022
d5a7fa6
Prepare for msaa texture rendering
soywiz Mar 24, 2022
9b91a0c
Fix KmlGlNative.renderbufferStorageMultisample types
soywiz Mar 24, 2022
037512d
Some optimizations
soywiz Mar 24, 2022
1bfdbf3
Merge branch 'main' into feature/gpu.rendering
soywiz Mar 24, 2022
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
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ val javaAddOpens = ArrayList<String>().apply {
add("--add-opens=java.desktop/sun.java2d.opengl=ALL-UNNAMED")
add("--add-opens=java.desktop/java.awt=ALL-UNNAMED")
add("--add-opens=java.desktop/sun.awt=ALL-UNNAMED")
if (isMacos) {
add("--add-opens=java.desktop/sun.lwawt.macosx=ALL-UNNAMED")
add("--add-opens=java.desktop/sun.lwawt=ALL-UNNAMED")
}
if (isLinux) add("--add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED")
}.toTypedArray()

Expand Down
4 changes: 2 additions & 2 deletions kmem/src/commonMain/kotlin/com/soywiz/kmem/Numbers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ public fun Float.toIntFloor(): Int = floor(this).toInt()
public fun Double.toIntFloor(): Int = floor(this).toInt()

/** Converts [this] into [Int] rounding to the nearest */
public fun Float.toIntRound(): Int = round(this).toLong().toInt()
public fun Float.toIntRound(): Int = round(this).toInt()
/** Converts [this] into [Int] rounding to the nearest */
public fun Double.toIntRound(): Int = round(this).toLong().toInt()
public fun Double.toIntRound(): Int = round(this).toInt()

/** Convert this [Long] into an [Int] but throws an [IllegalArgumentException] in the case that operation would produce an overflow */
public fun Long.toIntSafe(): Int = if (this in Int.MIN_VALUE.toLong()..Int.MAX_VALUE.toLong()) this.toInt() else throw IllegalArgumentException("Long doesn't fit Integer")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,10 @@ class KorgeExtension(val project: Project) {
add("--add-opens=java.desktop/sun.java2d.opengl=ALL-UNNAMED")
add("--add-opens=java.desktop/java.awt=ALL-UNNAMED")
add("--add-opens=java.desktop/sun.awt=ALL-UNNAMED")
if (isMacos) {
add("--add-opens=java.desktop/sun.lwawt=ALL-UNNAMED")
add("--add-opens=java.desktop/sun.lwawt.macosx=ALL-UNNAMED")
}
if (isLinux) add("--add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED")
}

Expand Down
3 changes: 2 additions & 1 deletion korge-sandbox/src/commonMain/kotlin/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ import kotlin.random.*
suspend fun main() = Korge(bgcolor = Colors.DARKCYAN.mix(Colors.BLACK, 0.8), clipBorders = false
//, debugAg = true
) {
mainGpuVectorRendering()
//mainFiltersRenderToBitmap()
mainBlur()
//mainBlur()
//mainCustomSolidRectShader()
//mainMipmaps()
//mainColorTransformFilter()
Expand Down
312 changes: 312 additions & 0 deletions korge-sandbox/src/commonMain/kotlin/MainGpuVectorRendering.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
import com.soywiz.kds.*
import com.soywiz.klogger.*
import com.soywiz.kmem.*
import com.soywiz.korag.*
import com.soywiz.korag.shader.*
import com.soywiz.korge.render.*
import com.soywiz.korge.view.*
import com.soywiz.korge.view.BlendMode
import com.soywiz.korim.bitmap.*
import com.soywiz.korim.color.*
import com.soywiz.korim.font.*
import com.soywiz.korim.format.*
import com.soywiz.korim.paint.*
import com.soywiz.korim.text.*
import com.soywiz.korim.vector.*
import com.soywiz.korim.vector.format.*
import com.soywiz.korio.file.std.*
import com.soywiz.korma.geom.*
import com.soywiz.korma.geom.shape.*
import com.soywiz.korma.geom.vector.*

suspend fun Stage.mainGpuVectorRendering() {
Console.log("[1]")
val korgeBitmap = resourcesVfs["korge.png"].readBitmap()
Console.log("[2]")
val tigerSvg = resourcesVfs["Ghostscript_Tiger.svg"].readSVG()
Console.log("[3]")
//AudioData(44100, AudioSamples(1, 1024)).toSound().play()

fun Context2d.buildGraphics() {
keep {
draw(tigerSvg)
translate(100, 200)
fill(Colors.BLUE) {
rect(-10, -10, 120, 120)
rectHole(40, 40, 80, 80)
}
fill(Colors.YELLOW) {
this.circle(100, 100, 40)
//rect(-100, -100, 500, 500)
//rectHole(40, 40, 320, 320)
}
fill(Colors.RED) {
regularPolygon(6, 30.0, x = 100.0, y = 100.0)
//rect(-100, -100, 500, 500)
//rectHole(40, 40, 320, 320)
}
}
keep {
translate(100, 20)
scale(2.0)
globalAlpha = 0.75
fillStyle = BitmapPaint(
korgeBitmap,
Matrix().translate(50, 50).scale(0.125),
cycleX = CycleMethod.REPEAT,
cycleY = CycleMethod.REPEAT
)
fillRect(0.0, 0.0, 100.0, 100.0)
fillStyle =
createLinearGradient(0.0, 0.0, 200.0, 200.0, transform = Matrix().scale(0.5).pretranslate(130, 30))
.addColorStop(0.0, Colors.RED)
.addColorStop(1.0, Colors.BLUE)
fillRect(100.0, 0.0, 100.0, 100.0)
}
keep {
font = DefaultTtfFont
fontSize = 16.0
fillStyle = Colors.WHITE
alignment = TextAlignment.TOP_LEFT
fillText("HELLO WORLD", 0.0, 16.0)
}
}

gpuShapeView { buildGraphics() }.xy(0, 0)//.rotation(45.degrees)
image(NativeImage(512, 512).context2d { buildGraphics() }).xy(700, 0)
image(Bitmap32(512, 512).context2d { buildGraphics() }).xy(700, 370)
}

inline fun Container.gpuShapeView(buildContext2d: Context2d.() -> Unit) =
GpuShapeView(buildShape { buildContext2d() }).addTo(this)

inline fun Container.gpuShapeView(shape: Shape, callback: @ViewDslMarker GpuShapeView.() -> Unit = {}) =
GpuShapeView(shape).addTo(this, callback)

class GpuShapeView(shape: Shape) : View() {
private val pointCache = FastIdentityMap<VectorPath, PointArrayList>()

var shape: Shape = shape
set(value) {
field = value
pointCache.clear()
}

companion object {
val u_Color = Uniform("u_Color", VarType.Float4)
val u_Transform = Uniform("u_Transform", VarType.Mat4)
val LAYOUT = VertexLayout(DefaultShaders.a_Pos)
val LAYOUT_FILL = VertexLayout(DefaultShaders.a_Pos, DefaultShaders.a_Tex)
val VERTEX_FILL = VertexShaderDefault {
SET(out, u_ProjMat * u_ViewMat * vec4(a_Pos, 0f.lit, 1f.lit))
SET(v_Tex, DefaultShaders.a_Tex)
}
val PROGRAM_STENCIL = Program(
vertex = VertexShaderDefault { SET(out, u_ProjMat * u_ViewMat * vec4(a_Pos, 0f.lit, 1f.lit)) },
fragment = FragmentShaderDefault { SET(out, vec4(1f.lit, 0f.lit, 0f.lit, 1f.lit)) },
)
val PROGRAM_COLOR = Program(
vertex = VERTEX_FILL,
fragment = FragmentShaderDefault {
SET(out, u_Color)
},
)
val PROGRAM_BITMAP = Program(
vertex = VERTEX_FILL,
fragment = FragmentShaderDefault {
// @TODO: we should convert 0..1 to texture slice coordinates
SET(out, texture2D(u_Tex, fract(vec2((vec4(v_Tex, 0f.lit, 1f.lit) * u_Transform)["xy"]))))
},
)
val PROGRAM_LINEAR_GRADIENT = Program(
vertex = VERTEX_FILL,
fragment = FragmentShaderDefault {
SET(out, texture2D(u_Tex, (vec4(v_Tex.x, v_Tex.y, 0f.lit, 1f.lit) * u_Transform)["xy"]))
},
)
}

private val bb = BoundsBuilder()
override fun getLocalBoundsInternal(out: Rectangle) {
shape.getBounds(out, bb)
}

var msaaSamples: Int = 4

override fun renderInternal(ctx: RenderContext) {
ctx.flush()
val currentRenderBuffer = ctx.ag.currentRenderBufferOrMain
ctx.renderToTexture(currentRenderBuffer.width, currentRenderBuffer.height, {
renderShape(ctx, shape)
}, hasStencil = true, msamples = msaaSamples) { texture ->
ctx.useBatcher {
it.drawQuad(texture, x = 0f, y = 0f)
}
}
}

private fun renderShape(ctx: RenderContext, shape: Shape) {
when (shape) {
EmptyShape -> Unit
is FillShape -> renderShape(ctx, shape)
is CompoundShape -> for (v in shape.components) renderShape(ctx, v)
is PolylineShape -> {
//println("TODO: PolylineShape not implemented. Convert into fills")
}
is TextShape -> renderShape(ctx, shape.primitiveShapes)
else -> TODO("shape=$shape")
}
}

private fun renderShape(ctx: RenderContext, shape: FillShape) {
val path = shape.path
val m = globalMatrix

val points = pointCache.getOrPut(path) {
val points = PointArrayList()
path.emitPoints2 { x, y, move ->
points.add(x, y)
}
points
}

val bb = BoundsBuilder()
bb.reset()

val data = FloatArray(points.size * 2 + 4)
for (n in 0 until points.size + 1) {
val x = points.getX(n % points.size).toFloat()
val y = points.getY(n % points.size).toFloat()
val tx = m.transformXf(x, y)
val ty = m.transformYf(x, y)
data[(n + 1) * 2 + 0] = tx
data[(n + 1) * 2 + 1] = ty
bb.add(tx, ty)
}
data[0] = ((bb.xmax + bb.xmin) / 2).toFloat()
data[1] = ((bb.ymax + bb.ymin) / 2).toFloat()

if (shape.path.winding != Winding.EVEN_ODD) {
error("Currently only supported EVEN_ODD winding")
}

val bounds = bb.getBounds()

ctx.dynamicVertexBufferPool { vertices ->
vertices.upload(data)
ctx.batch.updateStandardUniforms()

ctx.batch.simulateBatchStats(points.size + 2)

val scissor: AG.Scissor? = AG.Scissor().setTo(
//bounds
Rectangle.fromBounds(bounds.left.toInt(), bounds.top.toInt(), bounds.right.toIntCeil(), bounds.bottom.toIntCeil())
)
//val scissor: AG.Scissor? = null

ctx.ag.clearStencil(0, scissor = scissor)
//ctx.ag.clearStencil(0, scissor = null)
ctx.ag.draw(
vertices = vertices,
program = PROGRAM_STENCIL,
type = AG.DrawType.TRIANGLE_FAN,
vertexLayout = LAYOUT,
vertexCount = points.size + 2,
uniforms = ctx.batch.uniforms,
stencil = AG.StencilState(
enabled = true,
readMask = 0xFF,
compareMode = AG.CompareMode.ALWAYS,
referenceValue = 0xFF,
writeMask = 0xFF,
actionOnDepthFail = AG.StencilOp.KEEP,
actionOnDepthPassStencilFail = AG.StencilOp.KEEP,
actionOnBothPass = AG.StencilOp.INVERT,
),
blending = BlendMode.NONE.factors,
colorMask = AG.ColorMaskState(false, false, false, false),
scissor = scissor,
)
}
renderFill(ctx, shape.paint, shape.transform, bounds)
}

private val colorUniforms = AG.UniformValues()
private val bitmapUniforms = AG.UniformValues()
private val gradientUniforms = AG.UniformValues()
private val gradientBitmap = Bitmap32(256, 1)

private val colorF = FloatArray(4)

private fun renderFill(ctx: RenderContext, paint: Paint, transform: Matrix, bounds: Rectangle) {
if (paint is NonePaint) return

ctx.dynamicVertexBufferPool { vertices ->
val data = FloatArray(4 * 4)
var n = 0
val x0 = bounds.left.toFloat()
val y0 = bounds.top.toFloat()
val x1 = bounds.right.toFloat()
val y1 = bounds.bottom.toFloat()
val w = x1 - x0
val h = y1 - y0
data[n++] = x0; data[n++] = y0; data[n++] = 0f; data[n++] = 0f
data[n++] = x1; data[n++] = y0; data[n++] = w; data[n++] = 0f
data[n++] = x1; data[n++] = y1; data[n++] = w; data[n++] = h
data[n++] = x0; data[n++] = y1; data[n++] = 0f; data[n++] = h

vertices.upload(data)
ctx.useBatcher { batch ->
batch.updateStandardUniforms()
var uniforms: AG.UniformValues = colorUniforms
var program: Program = PROGRAM_COLOR
when (paint) {
is ColorPaint -> {
val color = paint
color.writeFloat(colorF)
colorUniforms[u_Color] = colorF
program = PROGRAM_COLOR
uniforms = colorUniforms
}
is BitmapPaint -> {
bitmapUniforms[DefaultShaders.u_Tex] = AG.TextureUnit(ctx.getTex(paint.bitmap).base)
val mat = (paint.transform * transform)
mat.scale(1.0 / paint.bitmap.width, 1.0 / paint.bitmap.height)
bitmapUniforms[u_Transform] = mat.toMatrix3D()
program = PROGRAM_BITMAP
uniforms = bitmapUniforms
}
is GradientPaint -> {
paint.fillColors(gradientBitmap.dataPremult)
gradientUniforms[DefaultShaders.u_Tex] = AG.TextureUnit(ctx.getTex(gradientBitmap).base)
val mat = paint.transform * transform * paint.gradientMatrixInv
gradientUniforms[u_Transform] = mat.toMatrix3D()
program = PROGRAM_LINEAR_GRADIENT
uniforms = gradientUniforms
}
else -> {
TODO("paint=$paint")
}
}
batch.setTemporalUniforms(uniforms) {
ctx.batch.simulateBatchStats(4)
ctx.ag.draw(
vertices = vertices,
program = program,
type = AG.DrawType.TRIANGLE_FAN,
vertexLayout = LAYOUT_FILL,
vertexCount = 4,
uniforms = ctx.batch.uniforms,
stencil = AG.StencilState(
enabled = true,
compareMode = AG.CompareMode.NOT_EQUAL,
writeMask = 0,
),
blending = BlendMode.NORMAL.factors,
colorMask = AG.ColorMaskState(true, true, true, true),
)
}
}
}
}
}
Loading