Skip to content

Commit ea4e78e

Browse files
committed
Drawing off memory and add different draw mode.
1 parent 1d212bb commit ea4e78e

File tree

18 files changed

+353
-28
lines changed

18 files changed

+353
-28
lines changed

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ asciidoctorj = "3.3.2"
33
clikt = "5.0.3"
44
gifencoder = "0.10.1"
55
jna = "5.14.0"
6-
kgl = "0.8.3"
6+
kgl = "0.8.4"
77
kotlin-coroutines = "1.10.2"
88
kotlin-ksp = "2.1.20-1.0.32"
99
kotlin-serialization = "2.1.20"

tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameEngine.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,20 @@ class GameEngine(
497497
}
498498
}
499499

500+
override fun renderAsBuffer(block: () -> Unit): FrameBuffer {
501+
// Render on the screen to clean up render states.
502+
render()
503+
504+
val renderFrame = platform.executeOffScreen(renderContext) {
505+
block.invoke()
506+
render()
507+
}
508+
509+
val buffer = FrameBuffer(gameOptions.width, gameOptions.height, gameOptions.colors())
510+
renderFrame.copyInto(buffer.colorIndexBuffer)
511+
return buffer
512+
}
513+
500514
/**
501515
* Will render the remaining operations on the screen.
502516
*/

tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameResourceAccess.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ interface GameResourceAccess {
5959
*/
6060
fun readFrame(): FrameBuffer
6161

62+
fun renderAsBuffer(block: () -> Unit): FrameBuffer
63+
6264
/**
6365
* Access a sprite sheet by its index.
6466
*/

tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/graphic/PixelArray.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,6 @@ class PixelArray(val width: Pixel, val height: Pixel, val pixelFormat: Int = Pix
131131
}
132132
}
133133

134-
// operator fun iterator(): Iterator<Int> = pixels.iterator()
135-
136134
fun fill(
137135
startX: Int,
138136
endX: Int,

tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/GfxLib.kt

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import com.github.mingdx.tiny.doc.TinyFunction
66
import com.github.mingdx.tiny.doc.TinyLib
77
import com.github.minigdx.tiny.engine.GameOptions
88
import com.github.minigdx.tiny.engine.GameResourceAccess
9+
import com.github.minigdx.tiny.platform.DrawingMode
910
import com.github.minigdx.tiny.render.operations.CameraOperation
1011
import com.github.minigdx.tiny.render.operations.ClipOperation
1112
import com.github.minigdx.tiny.render.operations.DitheringOperation
13+
import com.github.minigdx.tiny.render.operations.DrawingModeOperation
1214
import com.github.minigdx.tiny.render.operations.FrameBufferOperation
1315
import com.github.minigdx.tiny.render.operations.PaletteOperation
1416
import com.github.minigdx.tiny.resources.ResourceType
@@ -40,12 +42,59 @@ class GfxLib(private val resourceAccess: GameResourceAccess, private val gameOpt
4042
func["pset"] = pset()
4143
func["pget"] = pget()
4244
func["cls"] = cls()
45+
func["draw_mode"] = drawMode()
4346

4447
arg2["gfx"] = func
4548
arg2["package"]["loaded"]["gfx"] = func
4649
return func
4750
}
4851

52+
@TinyFunction(
53+
"""Switch to another draw mode.
54+
|- 0: default.
55+
|- 1: drawing with transparent (ie: can erase part of the screen)
56+
|- 2: drawing a stencil that will be use with the next mode
57+
|- 3: drawing using a stencil test (ie: drawing only in the stencil)
58+
|- 4: drawing using a stencil test (ie: drawing everywhere except in the stencil)
59+
""",
60+
)
61+
internal inner class drawMode : LibFunction() {
62+
private var current: Int = 0
63+
64+
private val modes = arrayOf(
65+
DrawingMode.DEFAULT,
66+
DrawingMode.ALPHA_BLEND,
67+
DrawingMode.STENCIL_WRITE,
68+
DrawingMode.STENCIL_TEST,
69+
DrawingMode.STENCIL_NOT_TEST,
70+
)
71+
72+
@TinyCall("Return the actual mode. Switch back to the default mode.")
73+
override fun call(): LuaValue {
74+
return valueOf(current).also {
75+
current = 0
76+
resourceAccess.addOp(DrawingModeOperation(modes[current]))
77+
}
78+
}
79+
80+
@TinyCall("Switch to another draw mode. Return the previous mode.")
81+
override fun call(
82+
@TinyArg("mode") a: LuaValue,
83+
): LuaValue {
84+
val before = current
85+
val index = a.checkint()
86+
// Invalid index
87+
if (index !in (0 until modes.size)) {
88+
return NIL
89+
}
90+
current = index
91+
val f = modes[current]
92+
resourceAccess.addOp(DrawingModeOperation(f))
93+
94+
return valueOf(before)
95+
}
96+
}
97+
4998
@TinyFunction("clear the screen", example = GFX_CLS_EXAMPLE)
5099
internal inner class cls : OneArgFunction() {
51100
@TinyCall("Clear the screen with a default color.")
@@ -105,18 +154,12 @@ class GfxLib(private val resourceAccess: GameResourceAccess, private val gameOpt
105154
"to_sheet",
106155
example = GFX_TO_SHEET_EXAMPLE,
107156
)
108-
inner class toSheet : OneArgFunction() {
157+
inner class toSheet : LibFunction() {
109158
@TinyCall("Copy the current frame buffer to an new or existing sheet index.")
110159
override fun call(
111-
@TinyArg("sheet") arg: LuaValue,
160+
@TinyArg("sheet") a: LuaValue,
112161
): LuaValue {
113-
val (index, name) = if (arg.isstring()) {
114-
val index = resourceAccess.spritesheet(arg.tojstring()) ?: resourceAccess.newSpritesheetIndex()
115-
index to arg.tojstring()
116-
} else {
117-
val spriteSheet = resourceAccess.spritesheet(arg.checkint())
118-
arg.toint() to (spriteSheet?.name ?: "frame_buffer_${arg.toint()}")
119-
}
162+
val (index, name) = getIndexAndName(a)
120163

121164
val frameBuffer = resourceAccess.readFrame()
122165
val sheet = SpriteSheet(
@@ -130,7 +173,43 @@ class GfxLib(private val resourceAccess: GameResourceAccess, private val gameOpt
130173
)
131174

132175
resourceAccess.spritesheet(sheet)
133-
return arg
176+
return valueOf(index)
177+
}
178+
179+
@TinyCall(
180+
"Create a blank spritesheet. " +
181+
"Execute the operation from the closure on the blank spritesheet and " +
182+
"copy it to an new or existing sheet index.",
183+
)
184+
override fun call(
185+
@TinyArg("sheet") a: LuaValue,
186+
@TinyArg("closure") b: LuaValue,
187+
): LuaValue {
188+
val (index, name) = getIndexAndName(a)
189+
val closure = b.checkclosure() ?: return call(a)
190+
191+
val frameBuffer = resourceAccess.renderAsBuffer { closure.invoke() }
192+
val sheet = SpriteSheet(
193+
0,
194+
index,
195+
name,
196+
ResourceType.GAME_SPRITESHEET,
197+
frameBuffer.colorIndexBuffer,
198+
frameBuffer.width,
199+
frameBuffer.height,
200+
)
201+
resourceAccess.spritesheet(sheet)
202+
return valueOf(index)
203+
}
204+
205+
private fun getIndexAndName(arg: LuaValue): Pair<Int, String> {
206+
return if (arg.isstring()) {
207+
val index = resourceAccess.spritesheet(arg.tojstring()) ?: resourceAccess.newSpritesheetIndex()
208+
index to arg.tojstring()
209+
} else {
210+
val spriteSheet = resourceAccess.spritesheet(arg.checkint())
211+
arg.toint() to (spriteSheet?.name ?: "frame_buffer_${arg.toint()}")
212+
}
134213
}
135214
}
136215

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.github.minigdx.tiny.platform
2+
3+
enum class DrawingMode {
4+
DEFAULT, // Default mode for drawing images
5+
ALPHA_BLEND, // Mode for drawing with transparency (alpha blending)
6+
STENCIL_WRITE, // Mode for writing to the stencil buffer
7+
STENCIL_TEST, // Mode for drawing based on the stencil buffer content
8+
STENCIL_NOT_TEST, // Mode for drawing based on the stencil buffer not written
9+
}

tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/platform/Platform.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,13 @@ interface Platform {
139139
* Draw the frame buffer on the screen.
140140
*/
141141
fun draw(renderContext: RenderContext)
142+
143+
/**
144+
* Execute the block using an off-screen buffer.
145+
* All drawing operation will use this off-screen buffer instead
146+
*/
147+
fun executeOffScreen(
148+
renderContext: RenderContext,
149+
block: () -> Unit,
150+
): RenderFrame
142151
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package com.github.minigdx.tiny.render
22

33
import com.github.minigdx.tiny.render.operations.DrawSprite
4+
import com.github.minigdx.tiny.render.operations.DrawingModeOperation
45

56
interface OperationsRender {
67
fun drawSprite(
78
context: RenderContext,
89
op: DrawSprite,
910
)
11+
12+
fun setDrawingMode(
13+
context: RenderContext,
14+
op: DrawingModeOperation,
15+
)
1016
}
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
package com.github.minigdx.tiny.render
22

3-
interface Render : ReadRender, WriteRender
3+
interface Render : ReadRender, WriteRender {
4+
fun executeOffScreen(
5+
context: RenderContext,
6+
block: () -> Unit,
7+
): RenderFrame
8+
}

tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/render/gl/OpenGLRender.kt

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ package com.github.minigdx.tiny.render.gl
33
import com.danielgergely.kgl.ByteBuffer
44
import com.danielgergely.kgl.GL_BLEND
55
import com.danielgergely.kgl.GL_COLOR_ATTACHMENT0
6+
import com.danielgergely.kgl.GL_COLOR_BUFFER_BIT
7+
import com.danielgergely.kgl.GL_DEPTH24_STENCIL8
8+
import com.danielgergely.kgl.GL_DEPTH_BUFFER_BIT
69
import com.danielgergely.kgl.GL_FRAMEBUFFER
710
import com.danielgergely.kgl.GL_FRAMEBUFFER_COMPLETE
811
import com.danielgergely.kgl.GL_NEAREST
9-
import com.danielgergely.kgl.GL_ONE_MINUS_SRC_ALPHA
12+
import com.danielgergely.kgl.GL_RENDERBUFFER
1013
import com.danielgergely.kgl.GL_RGBA
11-
import com.danielgergely.kgl.GL_SRC_ALPHA
14+
import com.danielgergely.kgl.GL_STENCIL_ATTACHMENT
15+
import com.danielgergely.kgl.GL_STENCIL_BUFFER_BIT
1216
import com.danielgergely.kgl.GL_TEXTURE_2D
1317
import com.danielgergely.kgl.GL_TEXTURE_MAG_FILTER
1418
import com.danielgergely.kgl.GL_TEXTURE_MIN_FILTER
@@ -36,14 +40,33 @@ class OpenGLRender(
3640
operationsShader.init(windowManager)
3741
framebufferShader.init(windowManager)
3842

43+
val onscreen = createNewFrameBuffer()
44+
val offscreen = createNewFrameBuffer()
45+
46+
return OpenGLRenderContext(
47+
windowManager = windowManager,
48+
currentFrameBuffer = onscreen,
49+
onscreenFrameBuffer = onscreen,
50+
offscreenFrameBuffer = offscreen,
51+
)
52+
}
53+
54+
private fun createNewFrameBuffer(): FrameBufferContext {
3955
// Framebuffer of the size of the screen
4056
val fboBuffer = ByteBuffer(gameOptions.width * gameOptions.height * PixelFormat.RGBA)
4157

4258
gl.enable(GL_BLEND)
4359

60+
// Attach stencil buffer to the framebuffer.
61+
val stencilBuffer = gl.createRenderbuffer()
62+
gl.bindRenderbuffer(GL_RENDERBUFFER, stencilBuffer)
63+
gl.renderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, gameOptions.width, gameOptions.height)
64+
4465
val fbo = gl.createFramebuffer()
4566
gl.bindFramebuffer(GL_FRAMEBUFFER, fbo)
4667

68+
gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, stencilBuffer)
69+
4770
// Prepare the texture used for the FBO
4871
val fboTexture = gl.createTexture()
4972
gl.bindTexture(GL_TEXTURE_2D, fboTexture)
@@ -68,13 +91,7 @@ class OpenGLRender(
6891
}
6992
gl.bindTexture(GL_TEXTURE_2D, null)
7093
gl.bindFramebuffer(GL_FRAMEBUFFER, null)
71-
72-
return OpenGLRenderContext(
73-
windowManager = windowManager,
74-
fbo = fbo,
75-
fboBuffer = fboBuffer,
76-
fboTexture = fboTexture,
77-
)
94+
return FrameBufferContext(fbo, fboBuffer, fboTexture)
7895
}
7996

8097
override fun render(
@@ -86,7 +103,6 @@ class OpenGLRender(
86103
gl.bindFramebuffer(GL_FRAMEBUFFER, context.fbo)
87104

88105
gl.viewport(0, 0, gameOptions.width, gameOptions.height)
89-
gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
90106

91107
operationsShader.render(context, ops)
92108

@@ -131,4 +147,25 @@ class OpenGLRender(
131147

132148
framebufferShader.drawOnScreen(context)
133149
}
150+
151+
override fun executeOffScreen(
152+
context: RenderContext,
153+
block: () -> Unit,
154+
): RenderFrame {
155+
context as OpenGLRenderContext
156+
context.useOffscreen()
157+
gl.bindFramebuffer(GL_FRAMEBUFFER, context.fbo)
158+
159+
gl.clearColor(0f, 0f, 0f, 0f)
160+
gl.clear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT or GL_STENCIL_BUFFER_BIT)
161+
162+
gl.bindFramebuffer(GL_FRAMEBUFFER, null)
163+
164+
block()
165+
val frame = readRender(context)
166+
167+
context.useOnscreen()
168+
169+
return frame
170+
}
134171
}

0 commit comments

Comments
 (0)