Skip to content

Commit

Permalink
Move the conversion from color index to color into the shader.
Browse files Browse the repository at this point in the history
Like this, only the index of the color is set on the CPU.
The conversion to color is set on the GPU and the engine doesn't need to pass over the full image again.
  • Loading branch information
dwursteisen committed Jan 15, 2024
1 parent c9408e3 commit 7015703
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import kotlin.math.abs
/**
* Color palette used by the game.
*
* Every colors from every resources will be aligned to use this color palette.
* Every color from every resource will be aligned to use this color palette.
* It means that if a color from a resource is not part of this color palette,
* this color will be replaced will the closed color from the palette.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ class Blender(private val gamePalette: ColorPalette) {
switch[gamePalette.check(source)] = gamePalette.check(target)
}

fun mix(colors: Array<ColorIndex>, x: Pixel, y: Pixel, transparency: Array<Int>?): Array<ColorIndex>? {
fun mix(colors: ByteArray, x: Pixel, y: Pixel, transparency: Array<Int>?): ByteArray? {
fun dither(pattern: Int): Boolean {
val a = x % 4
val b = (y % 4) * 4

return (pattern shr (15 - (a + b))) and 0x01 == 0x01
}

val color = gamePalette.check(colors[0])
colors[0] = switch[gamePalette.check(color)]
val color = gamePalette.check(colors[0].toInt())
colors[0] = switch[color].toByte()
// Return null if transparent
if (transparency == null && gamePalette.isTransparent(colors[0])) return null
if (transparency == null && gamePalette.isTransparent(colors[0].toInt())) return null
return if (!dither(dithering)) {
null
} else {
Expand Down Expand Up @@ -88,7 +88,7 @@ class FrameBuffer(

internal val camera = Camera()

private var tmp = Array(1) { 0 }
private var tmp = ByteArray(1) { 0 }

private val transparency = arrayOf(0)

Expand All @@ -103,9 +103,9 @@ class FrameBuffer(
val cy = camera.cy(y)
if (!clipper.isIn(cx, cy)) return

tmp[0] = gamePalette.check(colorIndex)
tmp[0] = gamePalette.check(colorIndex).toByte()
val index = blender.mix(tmp, cx, cy, transparency) ?: return
colorIndexBuffer.set(cx, cy, index[0])
colorIndexBuffer.set(cx, cy, index[0].toInt())
}

fun fill(startX: Pixel, endX: Pixel, y: Pixel, colorIndex: ColorIndex) {
Expand All @@ -129,7 +129,7 @@ class FrameBuffer(
pixel(x, y, colorIndex)
}
} else {
tmp[0] = color
tmp[0] = color.toByte()
val targetColor = blender.mix(tmp, 0, 0, transparency) ?: return
colorIndexBuffer.fill(left, right, cy, targetColor[0])
}
Expand Down Expand Up @@ -180,7 +180,7 @@ class FrameBuffer(
/**
* Blend function
*/
blender: (Array<Int>, Pixel, Pixel) -> Array<Int> = { colors, _, _ -> colors },
blender: (ByteArray, Pixel, Pixel) -> ByteArray = { colors, _, _ -> colors },
) {
val cx = camera.cx(dstX)
val cy = camera.cy(dstY)
Expand Down Expand Up @@ -209,23 +209,6 @@ class FrameBuffer(
* Create a buffer using the colorIndexBuffer as reference.
*/
fun generateBuffer(): ByteArray {
// Reset the old buffer
gifBuffer = IntArray(height * width)

var pos = 0
for (x in 0 until width) {
for (y in 0 until height) {
val index = colorIndexBuffer.getOne(x, y)
val color = gamePalette.getRGBA(index)

buffer[pos++] = color[0]
buffer[pos++] = color[1]
buffer[pos++] = color[2]
buffer[pos++] = color[3]

gifBuffer[x + y * width] = gamePalette.getRGAasInt(index)
}
}
return buffer
return this.colorIndexBuffer.pixels
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import kotlin.math.min

class PixelArray(val width: Pixel, val height: Pixel, val pixelFormat: Int = PixelFormat.INDEX) {

internal var pixels = Array(width * height * pixelFormat) { 0 }
internal var pixels = ByteArray(width * height * pixelFormat) { 0 }

val size = width * height * pixelFormat

private val tmp = Array(pixelFormat) { 0 }
private val tmp = ByteArray(pixelFormat) { 0 }

fun copyFrom(array: PixelArray) {
pixels = array.pixels.copyOf()
Expand All @@ -24,13 +24,13 @@ class PixelArray(val width: Pixel, val height: Pixel, val pixelFormat: Int = Pix
val ctop = 0 - top
val cbottom = bottom + 2 * ctop

pixels = Array(width * height * pixelFormat) { index ->
pixels = ByteArray(width * height * pixelFormat) { index ->
val p = index / pixelFormat
val x = p % width
val y = p / width

if (x in cleft..cright && y in ctop..cbottom) {
pixel
pixel.toByte()
} else {
0
}
Expand All @@ -47,13 +47,13 @@ class PixelArray(val width: Pixel, val height: Pixel, val pixelFormat: Int = Pix

val position = (correctedX + correctedY * width) * pixelFormat
pixel.forEachIndexed { index, value ->
pixels[position + index] = value
pixels[position + index] = value.toByte()
}
}

fun get(x: Pixel, y: Pixel): Array<Int> {
fun get(x: Pixel, y: Pixel): ByteArray {
assert(x >= 0 && x < width) { "x ($x) has to be between 0 and $width (excluded)" }
assert(y >= 0 && x < height) { "y ($y) has to be between 0 and $height (excluded)" }
assert(y >= 0 && y < height) { "y ($y) has to be between 0 and $height (excluded)" }
val position = (x + y * width) * pixelFormat
when (pixelFormat) {
PixelFormat.RGBA -> {
Expand Down Expand Up @@ -81,7 +81,7 @@ class PixelArray(val width: Pixel, val height: Pixel, val pixelFormat: Int = Pix
* The pixel format should be equals to 1 otherwise
* it will returns only the first component of the color.
*/
fun getOne(x: Pixel, y: Pixel): Int = get(x, y)[0]
fun getOne(x: Pixel, y: Pixel): Int = get(x, y)[0].toInt()

fun copyFrom(
source: PixelArray,
Expand All @@ -93,7 +93,7 @@ class PixelArray(val width: Pixel, val height: Pixel, val pixelFormat: Int = Pix
height: Pixel = this.height,
reverseX: Boolean = false,
reverseY: Boolean = false,
blender: (Array<Int>, Pixel, Pixel) -> Array<Int>?,
blender: (ByteArray, Pixel, Pixel) -> ByteArray?,
) {
assert(source.pixelFormat == pixelFormat) {
"Can't copy PixelArray because the pixel format is different between the two PixelArray"
Expand All @@ -103,14 +103,14 @@ class PixelArray(val width: Pixel, val height: Pixel, val pixelFormat: Int = Pix
val minHeight =
min(height, min(height - (dstY + height - this.height), height - (sourceY + height - source.height)))

(0 until minHeight).forEach { h ->
for (h in 0 until minHeight) {
val offsetY = if (reverseY) {
minHeight - h - 1
} else {
h
}

(0 until minWidth).forEach { w ->
for (w in 0 until minWidth) {
val dstPosition = (w + dstX + (h + dstY) * this.width) * pixelFormat

val offsetX = if (reverseX) {
Expand All @@ -136,9 +136,13 @@ class PixelArray(val width: Pixel, val height: Pixel, val pixelFormat: Int = Pix
}
}

operator fun iterator(): Iterator<Int> = pixels.iterator()
// operator fun iterator(): Iterator<Int> = pixels.iterator()

fun fill(startX: Int, endX: Int, y: Int, value: Int) {
fill(startX, endX, y, value.toByte())
}

fun fill(startX: Int, endX: Int, y: Int, value: Byte) {
val yy = (y * width * pixelFormat)
pixels.fill(value, yy + startX * pixelFormat, yy + endX * pixelFormat)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class SprLib(val gameOptions: GameOptions, val resourceAccess: GameResourceAcces
if (isInPixelArray(pixelArray, x, y)) {
val index = pixelArray.get(x, y)
val colorIndex = index.get(0)
return valueOf(colorIndex)
return valueOf(colorIndex.toInt())
} else {
return NIL
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,11 @@ class StdLib(
indexY * 4,
4,
4,
) { pixel: Array<Int>, _, _ ->
if (pixel[0] == 0) {
) { pixel: ByteArray, _, _ ->
if (pixel[0].toInt() == 0) {
pixel
} else {
pixel[0] = color
pixel[0] = color.toByte()
pixel
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ import com.danielgergely.kgl.GL_FLOAT
import com.danielgergely.kgl.GL_FRAGMENT_SHADER
import com.danielgergely.kgl.GL_LINK_STATUS
import com.danielgergely.kgl.GL_NEAREST
import com.danielgergely.kgl.GL_R8
import com.danielgergely.kgl.GL_RED
import com.danielgergely.kgl.GL_REPEAT
import com.danielgergely.kgl.GL_RGBA
import com.danielgergely.kgl.GL_STATIC_DRAW
import com.danielgergely.kgl.GL_TEXTURE0
import com.danielgergely.kgl.GL_TEXTURE1
import com.danielgergely.kgl.GL_TEXTURE_2D
import com.danielgergely.kgl.GL_TEXTURE_MAG_FILTER
import com.danielgergely.kgl.GL_TEXTURE_MIN_FILTER
Expand All @@ -26,6 +30,7 @@ import com.danielgergely.kgl.Kgl
import com.danielgergely.kgl.Shader
import com.github.minigdx.tiny.Pixel
import com.github.minigdx.tiny.engine.GameOptions
import com.github.minigdx.tiny.graphic.PixelFormat
import com.github.minigdx.tiny.log.Logger
import com.github.minigdx.tiny.platform.RenderContext
import com.github.minigdx.tiny.platform.WindowManager
Expand All @@ -36,14 +41,16 @@ class GLRender(
private val gameOptions: GameOptions,
) : Render {

private var buffer = ByteArray(0)

private val uvsData = FloatBuffer(
floatArrayOf(
2f,
0f,
0f,
2f,
0f,
0f,
0f,
2f,
),
)

Expand Down Expand Up @@ -73,9 +80,13 @@ class GLRender(
varying vec2 texture;
uniform sampler2D image;
uniform sampler2D colors;
void main() {
gl_FragColor = texture2D(image, texture);
vec4 point = texture2D(image, texture);
vec4 color = texture2D(colors, vec2(point.r, 1.0));
gl_FragColor = color;
}
""".trimIndent()

Expand Down Expand Up @@ -110,12 +121,12 @@ class GLRender(
gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)

val vertexData = floatArrayOf(
3f,
-1f,
-1f,
-3f,
3f,
1f,
-1f,
1f,
-1f,
)

val positionBuffer = gl.createBuffer()
Expand Down Expand Up @@ -154,10 +165,47 @@ class GLRender(
)
gl.enableVertexAttribArray(uvs)

val colors = gameOptions.colors()
// texture of one pixel height and 256 pixel width.
// one pixel of the texture = one index.
buffer = ByteArray(256 * 256 * PixelFormat.RGBA)
var pos = 0
for (y in 0 until 256) {
for (index in 0 until 256) {
val color = colors.getRGBA(index)

buffer[pos++] = color[0]
buffer[pos++] = color[1]
buffer[pos++] = color[2]
buffer[pos++] = color[3]
}
}
val index = gl.createTexture()
gl.bindTexture(GL_TEXTURE_2D, index)

gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)

gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
gl.texImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA,
256,
256,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
ByteBuffer(buffer),
)
gl.uniform1i(gl.getUniformLocation(shaderProgram, "colors")!!, 1)

return GLRenderContext(
windowManager = windowManager,
program = shaderProgram,
texture = gameTexture,
colors = index,
)
}

Expand Down Expand Up @@ -188,24 +236,26 @@ class GLRender(
gameOptions.height * gameOptions.zoom * context.windowManager.ratioHeight,
)

// -- game screen -- //
gl.activeTexture(GL_TEXTURE0)
gl.bindTexture(GL_TEXTURE_2D, context.texture)

gl.texImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA,
// I think that the texture format is not in the format OpenGL expect it (column first or line first)
// So I swap the height and the width so it's working even with non square game resolution.
height,
GL_R8,
width,
height,
0,
GL_RGBA,
GL_RED,
GL_UNSIGNED_BYTE,
ByteBuffer(image),
)

gl.uniform1i(gl.getUniformLocation(context.program, "image")!!, 0)

gl.activeTexture(GL_TEXTURE1)
gl.bindTexture(GL_TEXTURE_2D, context.colors)

gl.clear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)

gl.clearColor(0f, 0f, 0f, 1.0f)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ import com.github.minigdx.tiny.platform.WindowManager
class GLRenderContext(
val program: Program,
val texture: Texture,
val colors: Texture,
val windowManager: WindowManager,
) : RenderContext
Loading

0 comments on commit 7015703

Please sign in to comment.