luv

Workshop wiki

frame-performance.lisp

luvcraft/frame-performance.lisp

system luvcraft/core · 24 definitions · on GitHub

Small, explicit measurements of the actual luvcraft frame path.

in-package#:luvcraft
luv.arithmetic.records:define-quantity-struct
luvcraft-frame-sample
:constructormake-luvcraft-frame-sample
frame-seconds0d0:typedouble-float:quantity
:quantity:frame-cpu-duration:unit:second
simulation-seconds0d0:typedouble-float:quantity
:quantity:simulation-duration:unit:second
streaming-seconds0d0:typedouble-float:quantity
:quantity:streaming-duration:unit:second
presentation-seconds0d0:typedouble-float:quantity
:quantity:presentation-duration:unit:second
shader-refresh-seconds0d0:typedouble-float:quantity
:quantity:shader-refresh-duration:unit:second
mesh-publication-seconds0d0:typedouble-float:quantity
:quantity:mesh-publication-duration:unit:second
uniform-seconds0d0:typedouble-float:quantity
:quantity:uniform-update-duration:unit:second
shadow-encode-seconds0d0:typedouble-float:quantity
:quantity:shadow-encode-duration:unit:second
scene-encode-seconds0d0:typedouble-float:quantity
:quantity:scene-encode-duration:unit:second
surface-copy-encode-seconds0d0:typedouble-float:quantity
:quantity:surface-copy-encode-duration:unit:second
resident-chunk-count0:typefixnum
pending-production-count0:typefixnum
staged-chunk-count0:typefixnum
chunk-count0:typefixnum
draw-count0:typefixnum
vertex-count0:typefixnum
defmacrowith-luvcraft-frame-timing
sampleaccessorzone
&bodybody

Accumulate body into sample and expose it as nested CPU trace zone.

let
record
gensym"SAMPLE"
started
gensym"STARTED"
`
with-cpu-trace-zone
let*
,record,sample
,started
and,record
get-internal-real-time

One body serves sampled and unsampled frames. Besides making the

semantic guarantee obvious, this keeps a large render pass from

appearing twice in the compiler IR at every timing boundary.

unwind-protect
progn,@body
when,record
incf
,accessor,record
/
-
get-internal-real-time
,started
coerceinternal-time-units-per-second'double-float

Tracy watches the same frame path live, where the sample struct above watches a fixed batch of frames and prints the result. The two are meant to answer different questions: the benchmark says whether a change moved the numbers, and Tracy says where a frame that felt wrong actually went.

defparameter*luvcraft-tracy-plots*'
"resident chunks""pending production""staged chunks""drawable chunks""draws""vertices""player chunk x""player chunk z""frame CPU ms""60 Hz budget ms"
"The per-frame counts luvcraft draws alongside its Tracy zones."
defvar*luvcraft-tracy-plots-described-p*nil"Whether the connected viewer has been told how to draw luvcraft's plots."
defunstart-luvcraft-tracy

Start Tracy for luvcraft and answer whether the profiler is running.

Nothing is recorded until a viewer connects, so this is safe to leave on. Call it before starting a session if you want the production workers to appear under their own names: a thread can only introduce itself to a profiler that is already running, and luvcraft's workers introduce themselves as they start.

start-tracy:application-name"luvcraft"
*tracy*
defvar*luvcraft-tracy-capture-controller*nil"The shared subprocess owner for this Lisp application's captures."
defvar*luvcraft-tracy-capture-controller-lock*
sb-thread:make-mutex:name"Luvcraft Tracy capture controller"
defunmake-luvcraft-tracy-capture-controller
luv.tracy.capture:make-tracy-capture-controller:application-name"luvcraft":directory
merge-pathnames"build/tracy/"
asdf:system-source-directory"luvcraft"
defunluvcraft-tracy-capture-active-p

Whether Luvcraft's capture is starting, recording, or freezing its trace.

defunstart-luvcraft-tracy-capture

Publish an asynchronous capture start and return its reserved pathname.

defunstop-luvcraft-tracy-capture

Publish one graceful stop without waiting for trace finalization.

defunopen-luvcraft-tracy-capture
&optionalpathname

Open pathname or Luvcraft's last frozen trace without blocking the caller.

defunreveal-luvcraft-tracy-capture
&optionalpathname

Reveal pathname or Luvcraft's last frozen trace without blocking the caller.

defunrelease-luvcraft-tracy-capture-controller

Terminally detach Luvcraft's current controller without waiting on it.

Detachment is atomic and precedes release, so a later game session obtains a fresh controller even while the prior capture finishes on its owner thread.

defundescribe-luvcraft-tracy-plots

Describe luvcraft's plots to a viewer, once per connection.

Plot configuration reaches only a viewer that is already listening, and a capture may begin at any frame, so the description is re-sent on each fresh connection rather than once at startup.

let
cond

A capture launched from F9 initializes Tracy on its control worker. Name the actual rendering lane when it first observes that viewer rather than leaving the control worker called main.

dolist
configure-tracy-plotplot:format:number:stept
luv.arithmetic.records:define-quantity-structluvcraft-frame-benchmark
backend:metal:typekeyword
scenario:steady:typekeyword
device"":typestring
width0:typefixnum
height0:typefixnum
presentation-width0:typefixnum
presentation-height0:typefixnum
warmup-count0:typefixnum
samples
:typevector
completion-seconds0d0:typedouble-float:quantity
:quantity:benchmark-completion-duration:unit:second
drain-seconds0d0:typedouble-float:quantity
:quantity:benchmark-drain-duration:unit:second
desired-chunk-count0:typefixnum
entering-chunk-count0:typefixnum
settled-framenil:type
ornullfixnum
defunluvcraft-frame-samples-metric-summary
samplesreader

Return median, p95, mean, and maximum milliseconds over samples.

let*
values
map'vector
lambda
*1000d0
funcallreadersample
samples
count
lengthvalues
when
zeropcount
error"Cannot summarize an empty luvcraft frame benchmark."
let*
sorted
sort
copy-seqvalues
#'<
median-index
floorcount2
p95-rank
ceiling
*0.95d0count
p95-index
min
1-count
max0
1-p95-rank
median
if
oddpcount
arefsortedmedian-index
/
+
arefsorted
1-median-index
arefsortedmedian-index
2d0
valuesmedian
arefsortedp95-index
/
reduce#'+values
count
arefsorted
1-count
defunluvcraft-frame-metric-summary
benchmarkreader

Return the median, p95, mean, and maximum milliseconds for reader.

luvcraft-frame-samples-metric-summary
luvcraft-frame-benchmark-samplesbenchmark
reader
defunluvcraft-frame-benchmark-transition-samples
benchmark

Return measured samples up to streaming settlement, or all when unsettled.

let*
samples
luvcraft-frame-benchmark-samplesbenchmark
settled
luvcraft-frame-benchmark-settled-framebenchmark
subseqsamples0
ifsettled
min
lengthsamples
1+settled
lengthsamples
defparameter*luvcraft-frame-metrics*`
"frame CPU".,#'luvcraft-frame-sample-frame-seconds
"simulation".,#'luvcraft-frame-sample-simulation-seconds
"streaming".,#'luvcraft-frame-sample-streaming-seconds
"present (inclusive)".,#'luvcraft-frame-sample-presentation-seconds
"shader refresh".,#'luvcraft-frame-sample-shader-refresh-seconds
"mesh publication".,#'luvcraft-frame-sample-mesh-publication-seconds
"uniform update".,#'luvcraft-frame-sample-uniform-seconds
"shadow encode".,#'luvcraft-frame-sample-shadow-encode-seconds
"scene encode".,#'luvcraft-frame-sample-scene-encode-seconds
"surface copy encode".,#'luvcraft-frame-sample-surface-copy-encode-seconds
defunprint-luvcraft-frame-benchmark
benchmark&optional
stream*standard-output*

Print a bounded human-readable summary of benchmark.

let*
samples
luvcraft-frame-benchmark-samplesbenchmark
count
lengthsamples
first
and
pluspcount
arefsamples0
completion
luvcraft-frame-benchmark-completion-secondsbenchmark
formatstream"luvcraft ~:(~A~) frame benchmark~%"
luvcraft-frame-benchmark-backendbenchmark
formatstream" device: ~A~%"
luvcraft-frame-benchmark-devicebenchmark
formatstream" scenario: ~(~A~)~%"
luvcraft-frame-benchmark-scenariobenchmark
formatstream" scene: ~Dx~D; presentation: ~Dx~D, ~D warmup + ~D measured~%"
luvcraft-frame-benchmark-widthbenchmark
luvcraft-frame-benchmark-heightbenchmark
luvcraft-frame-benchmark-presentation-widthbenchmark
luvcraft-frame-benchmark-presentation-heightbenchmark
luvcraft-frame-benchmark-warmup-countbenchmark
count
whenfirst
formatstream" first measured: ~D/~D chunks, ~D draws, ~:D vertices~%"
luvcraft-frame-sample-chunk-countfirst
luvcraft-frame-benchmark-desired-chunk-countbenchmark
luvcraft-frame-sample-draw-countfirst
luvcraft-frame-sample-vertex-countfirst
formatstream"~% CPU metric median p95 mean max~%"
dolist
multiple-value-bind
medianp95meanmaximum
formatstream" ~24A ~7,3F ~7,3F ~7,3F ~7,3F ms~%"
carmetric
medianp95meanmaximum
when
eq:streaming
luvcraft-frame-benchmark-scenariobenchmark
let*
transition-count
lengthtransition
settled
luvcraft-frame-benchmark-settled-framebenchmark
multiple-value-bind
medianp95meanmaximum
luvcraft-frame-samples-metric-summarytransition#'luvcraft-frame-sample-frame-seconds
formatstream"~% streaming transition: ~D entering chunks, ~D frames~%"
luvcraft-frame-benchmark-entering-chunk-countbenchmark
transition-count
formatstream" settled: ~:[not within measured batch~;frame ~:*~D~]~%"settled
formatstream" frame CPU: median ~,3F, p95 ~,3F, max ~,3F ms (~,1F frames/s mean)~%"medianp95maximum
/1000d0mean
formatstream" 60 Hz deadline misses: ~D/~D~%"
count-if
lambda
>
luvcraft-frame-sample-frame-secondssample
/1d060d0
transition
transition-count
formatstream"~% completion-limited: ~,3F ms/frame (~,1F frames/s)~%"
*1000d0
/completioncount
/countcompletion
formatstream" final queue drain: ~,3F ms~%"
*1000d0
luvcraft-frame-benchmark-drain-secondsbenchmark
formatstream

note: completion-limited time is submit-to-shared-event wall time, ~ not a GPU timestamp.~%

benchmark
defunwrite-luvcraft-frame-benchmark-csv
benchmarkpathname

Write every benchmark sample as stable, comparison-friendly CSV.

ensure-directories-existpathname
with-open-file
streampathname:direction:output:if-exists:supersede
formatstream"frame,frame_cpu_ms,simulation_ms,streaming_ms,present_ms,shader_refresh_ms,mesh_publication_ms,uniform_ms,shadow_encode_ms,scene_encode_ms,surface_copy_encode_ms,resident_chunks,pending_production,staged_chunks,chunks,draws,vertices~%"
loopforsampleacross
luvcraft-frame-benchmark-samplesbenchmark
forindexfrom0do
formatstream"~D,~,6F,~,6F,~,6F,~,6F,~,6F,~,6F,~,6F,~,6F,~,6F,~,6F,~D,~D,~D,~D,~D,~D~%"index
*1000d0
luvcraft-frame-sample-frame-secondssample
*1000d0
luvcraft-frame-sample-simulation-secondssample
*1000d0
luvcraft-frame-sample-streaming-secondssample
*1000d0
luvcraft-frame-sample-presentation-secondssample
*1000d0
luvcraft-frame-sample-shader-refresh-secondssample
*1000d0
luvcraft-frame-sample-mesh-publication-secondssample
*1000d0
luvcraft-frame-sample-uniform-secondssample
*1000d0
luvcraft-frame-sample-shadow-encode-secondssample
*1000d0
luvcraft-frame-sample-scene-encode-secondssample
*1000d0
luvcraft-frame-sample-surface-copy-encode-secondssample
luvcraft-frame-sample-resident-chunk-countsample
luvcraft-frame-sample-pending-production-countsample
luvcraft-frame-sample-staged-chunk-countsample
luvcraft-frame-sample-chunk-countsample
luvcraft-frame-sample-draw-countsample
luvcraft-frame-sample-vertex-countsample
pathname