Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
26 changes: 18 additions & 8 deletions frameos/src/apps/render/image/app.nim
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ type
App* = ref object of AppRoot
appConfig*: AppConfig

proc clearTransientInputs(self: App) =
self.appConfig.inputImage = none(Image)
self.appConfig.image = nil

proc render*(self: App, context: ExecutionContext, image: Image) =
try:
let sourceImage = self.appConfig.image
Expand Down Expand Up @@ -53,13 +57,19 @@ proc render*(self: App, context: ExecutionContext, image: Image) =
scaleAndDrawImage(image, errorImage, self.appConfig.placement)

proc run*(self: App, context: ExecutionContext) =
render(self, context, context.image)
try:
render(self, context, context.image)
finally:
self.clearTransientInputs()

proc get*(self: App, context: ExecutionContext): Image =
result = if self.appConfig.inputImage.isSome:
self.appConfig.inputImage.get()
elif context.hasImage:
newImage(context.image.width, context.image.height)
else:
newImage(self.frameConfig.renderWidth(), self.frameConfig.renderHeight())
render(self, context, result)
try:
result = if self.appConfig.inputImage.isSome:
self.appConfig.inputImage.get()
elif context.hasImage:
newImage(context.image.width, context.image.height)
else:
newImage(self.frameConfig.renderWidth(), self.frameConfig.renderHeight())
render(self, context, result)
finally:
self.clearTransientInputs()
30 changes: 29 additions & 1 deletion frameos/src/apps/render/image/tests/test_app.nim
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ suite "render/image app":
test "run renders onto context image in place":
let source = newImage(1, 1)
source.data[0] = rgbx(200, 100, 50, 255)
let input = newImage(3, 2)
let config = makeConfig(3, 2)
let app = App(
frameConfig: config,
Expand All @@ -58,14 +59,41 @@ suite "render/image app":
blendMode: "normal"
)
)
let context = ExecutionContext(image: newImage(3, 2), hasImage: true)
let context = ExecutionContext(image: input, hasImage: true)

app.run(context)

let sample = pixel(context.image, 2, 1)
check sample.r > 0
check sample.g > 0
check sample.b > 0
check app.appConfig.image.isNil
check app.appConfig.inputImage.isNone

test "get clears transient source images after returning output":
let source = newImage(1, 1)
source.data[0] = rgbx(50, 200, 100, 255)
let input = newImage(3, 2)
let config = makeConfig(3, 2)
let app = App(
frameConfig: config,
scene: makeScene(config),
appConfig: AppConfig(
inputImage: some(input),
image: source,
placement: "stretch",
offsetX: 0,
offsetY: 0,
blendMode: "normal"
)
)

let output = app.get(ExecutionContext(hasImage: false))

check output == input
check pixel(output, 2, 1).g > 0
check app.appConfig.image.isNil
check app.appConfig.inputImage.isNone

test "missing source image is handled by error path without raising":
let config = makeConfig(4, 3)
Expand Down
9 changes: 8 additions & 1 deletion frameos/src/frameos/runner.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import frameos/utils/time
import frameos/scenes
import frameos/boot_guard
import frameos/runtime_diagnostics
import frameos/utils/memory
import frameos/watchdog

import drivers/drivers as drivers
Expand Down Expand Up @@ -225,7 +226,9 @@ proc startRenderLoop*(self: RunnerThread, maxCycles = -1): Future[void] {.async.
self.triggerRenderNext = false # used to debounce render events received while rendering

let interval = currentScene.refreshInterval
let (lastRotatedImage, nextSleep) = self.renderSceneImage(exportedScene.get(), currentScene)
var renderResult = self.renderSceneImage(exportedScene.get(), currentScene)
var lastRotatedImage = renderResult[0]
let nextSleep = renderResult[1]
reclaimRetiredExportedScenes(currentExportedScenesGeneration(), self.logger)
clearBootCrashCount()
successfulSceneRenders += 1
Expand Down Expand Up @@ -261,6 +264,10 @@ proc startRenderLoop*(self: RunnerThread, maxCycles = -1): Future[void] {.async.
except Exception as e:
self.logger.log(%*{"event": "render:driver:error", "error": $e.msg, "stacktrace": e.getStackTrace()})
finally:
lastRotatedImage = nil
renderResult[0] = nil
if self.frameConfig.device == "framebuffer":
reclaimRenderMemory()
clearNextRenderSeconds()
markRuntimeDone()

Expand Down
9 changes: 9 additions & 0 deletions frameos/src/frameos/utils/memory.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
proc reclaimRenderMemory*() =
## Large image decodes can leave sizeable temporary allocations behind.
## Collect unreachable Nim objects first, then ask glibc malloc to return
## free heap pages to the OS on Linux frame targets.
GC_fullCollect()

when defined(linux):
proc malloc_trim(pad: csize_t): cint {.importc: "malloc_trim", header: "<malloc.h>".}
discard malloc_trim(0.csize_t)
Loading