Skip to content

Latest commit

 

History

History
533 lines (387 loc) · 21.8 KB

TODO.org

File metadata and controls

533 lines (387 loc) · 21.8 KB

TODO

Design notes

4-layer graphics API

The idea is to have a layered API structure, in which all layers are usable to engine’s users, and will be selected by convenience.

Layers

Layer 0 - cl-opengl (and SDL2/SDL2-ttf)

Enough said. The engine will not pretend it’s working with something else, and will allow/support direct OpenGL calls and access to OpenGL properties as much as possible.

The same idea applies to other dependencies that are directly related to drawing - e.g. image loading code.

This layer is already present and is not part of the engine code.

Layer 1 - convenience wrappers around OpenGL calls

Lowest-level utilities wrapping together OpenGL operations in an interface that’s more convenient for most common operations.

Layer 2 - object-mode high-level drawing API

Wrapping layers 0 and 1 in an object-based code that allows for convenient reuse of stateful data while carefully managing state underneath.

Not sure what’s the extent of resource management here. In particular, should the engine automagically manage resources like textures or image surfaces? Should caching be implemented underneath, so that e.g. image textue data is shared between instances of drawable quads?

Should we split those two into sublayers of a) resources, and b) drawable objects?

Or maybe provide layers for both automagically managed objects and not managed objects?

Resources: image, texture. Drawable: textured quad.

Layer 3 - immediate-mode high-level drawing API

A high-level API for drawing things without having to manage any state.

Primarily done in terms of underlying API, will manage its resources to ensure maximum efficiency and minimum memory use.

Open questions

  • Where do e.g. particle systems fit in? As Layer-2 drawable objects?

Drawables I need right now

How about we keep working on games and see what pops up?

Asset management

I want an asset management system that’s flexible, but does not introduce any unnecessary cognitive overhead into the game code.

Critical assumption: an asset is always available as long as the handle exists (and is valid). Behind the scenes, the engine needs to be able to manage data so that the asset is ready when needed, including recreating it if it was somehow paged out.

Error handling policy

2 goals:

  • for development, we want errors to be loud and clear -> log + condition, debugger break
  • for release, we want errors to be silent (non-crashing) -> log + some default values provided (e.g. default texture)

Continuable restarts in development mode for things that can be fixed - e.g. restart for loading a resource to provide a different path.

Things to do

Functionality

Generic debugging switch

Something to use to toggle profiling, auto-saving perf counters, engine-level debug keys, etc.

vsync control

Make a function for enabling/disabling vsync using #’sdl2:gl-set-swap-interval.

NOTE make sure you check error values!

Matrix operations

Migrating drawing to OpenGL core profile

Drawing textured quads

Add error handling

Configurable min/mag filters (and maybe wrap)

Render-to-texture

Give the ability to somehow flip the texture

Either on load or on display. Point being, SDL_ttf seems to render text upside down and in weird format (BGRA maybe?). So this also needs proper handling of SDL_Surface image data.

Writing text

Need an API proposal.

Simplest API proposal - direct drawing:

(draw-text "Hello world" :position position)  ; uses default font and size
(draw-text "Hello world" :x x :y y)     ; uses default font and size
(draw-text "Hello world" :x x :y y :size size) ; text with non-default size
(draw-text "Hello world" :position position :font some-font) ; uses specific font

(NOTE maybe draw-text* for :x :y, and draw-text for position?)

How to draw text

  • load font font types:
    • ttf / open font types / etc.
    • bitmap font (texture)

Code for convenient display of text rendered to texture

(far future) Bitmap font handling under the same abstraction

ERROR HANDLING

Sort out caching issues for fonts

Test performance of rendering some texts each frame

Create an object/method structure for the drawable text that font drawing is supposed to return

Maybe move fonts to font.lisp, or sth.

Maybe even s/font/face/, because it seems that our “font” might be a combination of font + size.

NOTE RE sizes

Maybe we can design the API so that rendered font’s size is the “best” size to use, but have the API rescale text automagically to fit in pixel size.

Also NOTE that while in testing, passed font’s height was 42 (pt, but SDL_ttf documentation says it’s essentially equivalent to px), the resulting texture was 47px high. Need to find out if this is some kind of border, or overline/underline magic, or what? Checking TTF_FontHeight would be cool, but that might require patching the bindings up.

Logging

Configuration

Something to read configs from an external source. Also that would make games avoid writing over global engine variables so much.

Asset management

Needs to work with both bundled games and ones run interactively from sources.

ECS

We’ll be implementing an entity/component/system architecture for managing the game.

Test scene (default-game)

Make it display engine name

Make it display engine logo

Make a logo

Add an FPS counter

UI Overlays

FPS counter overlay

FPS graph overlay

(Maybe) Keyboard/Mouse events overlay

(showing last few events that happened)

Utilities

[#A] Performance counters improvements

Consider adding “sampling time designators”

  • :frame - sample every full frame
  • :tick - sample every game tick (including every full frame if not in fixed-timestep mode) Or maybe just have the user use :frame when they know their game is not using a fixed timestep.

[#C] Find a way to unify horizontal scale of counters for readability.

[#B] Maybe make a way to “snapshot” counters?

Either deep-copy, or add a function that’ll dump the state of counters at runtime to a uniquely-named file, in order to save the “context” when game starts to lag.

Actually, binding write-counter-report (+ name generation) to a keypress event should be enough.

Done; :after methods FTW.

[#B] Add a more convenient counter summary - a sortable table maybe, w/ stat values visible

Something like:
Counter nameIntervalIncrementsSample
MIN / RAVG / MAXMIN / RAVG / MAX
Counter 1frame1.0 / 10.0 / 100.01.23 / 12.3 / 123.0

[#C] Maybe make counters auto-dump a report if game crashes for some reason

[#C] Make ring buffers return only real values

Not that important, but useful for long sampling intervals and quick profiling sessions.

[#C] Make running average ignore initial zeroes

Not that important, but useful for long sampling intervals and quick profiling sessions.

Color wrapper

Something to make using colors better / more convenient.

Basic collision detection utilities

Box-box intersection

Box-sphere intersection

Box-line intersection

Sphere-line intersection

Something for more complex shapes

A simple layout engine for drawables.

Something that would let me compose drawables (esp. text) and align them to each other instead of specifying positions manually.

Internal profiler with output to chrome://tracing.

Notes for internal profiler concept:

  • You can probably collect samples in thread-local vars to avoid synchronization issues, and only merge them together when saving to JSON.
  • Consider creating fixed-length buffers for samples and streaming them to files regularly, to avoid excessive memory usage. Note that 100x 1kb x 60 frames x 60 seconds = 360MB / minute.
  • Also, fixed-length buffers would be great for avoiding generating lots of additional work for GC.
  • Consider having start-profiling / stop-profiling functions that will let you start/stop recording, so you only generate samples you’re interested in.
  • Try and not do all of these at once; a profiler like this seems to be a complicated task, and you’re supposed to be writing games, not performance debugging tools ;).

Tracking of resource leaks

Scaffolding is done; what remains is integrating this into some sane resource management framework.

Some generalized resource manager

Something to free engine user from having to manually free resources (esp. drawables). Not sure if makes sense yet, maybe it’s better to stick to manual management for now.

Probably tracking will get integrated into it.

Debug view - basic performance counter graphs

I’m interested in following primary performance counters:

  • average render frame duration
  • average updates per render frame

Represented as both number and a graph of change per second. Also I might be interested in graphing the sequence of update and render events in the last second.

Moreover, in the future, I may be interested in tracking down number of arbitrary events, so a generalized performance counter API could be in order.

NOTE that performance counters may count some of THE SAME data that will also be collected by the internal profiler (chrome://tracing), so it may be worth to consider to merge the two concepts together.

Performance counters - output to file

Instead of in-game graph, consider (maybe as a prototype) simply dumping perf counter data to a file to be graphed later.

Optimizations

Optimize math library

It’ll take some serious work (esp. with getting it not to crash or fail on type checks); maybe it’s more worth it to switch to rtg-math.

Think of some optimizations to ECS - stuff there can be on the critical path

In particular, in TSWR: Asteroids I’m using an ECS system for collision detection.

Consider running gc in the main loop every now and then

First, debug the memory profile of the engine / games running on top of it, and then implement it if necessary.

Far Future (AKA someday/maybe)

Drawing text

What I want my text to handle, at least in the future:

  • multiline rendering to a box of given size (in pixels)
  • alignment within a box of given size (in pixels)
  • policies for said box (let the text spill out, or actually do overflow: hidden)
  • colors
  • bold, italics, underline, overline
  • being able to access the text and replace some of its parts with a different one (for things like on-hover effects)
  • all features need to be somewhat supported by both bitmap and rendered fonts transparently, so that - past loading a font - I don’t have to care about type of font anymore

Some DSL like:

(:align :right :effect :underline "Hello World")
(:align :center :color #x11FF11 :id "affaff" "Maka paka")
("Foo" (:on-hover (lambda (_this) "Quux") "Bar"))
("Foo" (:on-hover (lambda (this) (:color "red" (text this))) "Bar!"))

Also, aligning/positioning DSL would be cute.

Signed distance field text rendering

See https://github.com/libgdx/libgdx/wiki/Distance-field-fonts for details.

sb-sprof viewer

Some nice web-based viewer UI for SBCL’s sb-sprof, using https://datatables.net/ to navigate the dump.

9-patch sprite support

Useful for e.g. resizable UI elements.

Consider something like AntTweakBar for manipulating engine/game params at runtime

http://anttweakbar.sourceforge.net/doc/

Or maybe integrating with precisely it!

Bonus feature: I’d like to be able to dump the changes to string / file, so that I could use it to experiment with stuff at runtime, and then easily list changes to introduce to code.

Make SLIME/SWANK work correctly with the engine

SLIME/SWANK-based console (or maybe just in-game Lisp console)

Either way, that would need to have some utilities for discovery. Maybe we should map things available for cmdlist / cvarlist with annotations in code? Actually, that could also make sense with the GUI AntTweakBar idea.

Something like:

@Tweakable (:type :color)
(defparameter +default-enemy-color+ (make-color "#11FF11") "Default enemy color.")

Leading to both it being available as cvar, and as a color gauge in the Tweak Bar, with the description taken from the docstring.

Bugs

Stored in issues.

Investigate apparent (unexpected) vsync in test run.

Debug performance counters keep giving me something like this (with step temporarily limited to 1/50s):

<DEBUG> [00:03:47] p2d main.lisp (run-main-loop) - In last second, executed 50 steps over 16 milliseconds, AVG = 0.32 ms/step. <DEBUG> [00:03:47] p2d main.lisp (run-main-loop) - Rendered 60 frames over 983 milliseconds, AVG = 16.383333 ms/frame.

It’s like there’s vsync enabled somewhere by default. I want to control that setting.

NOTE: (sdl2:gl-get-swap-interval) returns 1 - vsync is on, apparently. NOTE2: (sdl2:gl-set-swap-interval) controls vsync.

Fix dt values passed to #’on-idle and #’on-render.

Right now they’ll only work with a fixed step loop.

(potential) Disabling vsync makes steps run absurdly fast compared to when vsync is enabled

<DEBUG> [00:22:59] p2d main.lisp (run-main-loop) - In last second, executed 60 steps over 6 milliseconds, AVG = 0.1 ms/step. <DEBUG> [00:22:59] p2d main.lisp (run-main-loop) - Rendered 2732 frames over 981 milliseconds, AVG = 0.3590776 ms/frame.

Compare vs. vsync enabled:

<DEBUG> [00:25:07] p2d main.lisp (run-main-loop) - In last second, executed 60 steps over 50 milliseconds, AVG = 0.8333333 ms/step. <DEBUG> [00:25:07] p2d main.lisp (run-main-loop) - Rendered 60 frames over 949 milliseconds, AVG = 15.816667 ms/frame.

Might be the artifact of the way I calculate step time though. I.e. bug in the debug counters.

#’sdl2:gl-get-swap-interval seems to signal a Lisp error on my VM

Need to investigate why, and - if necessary - enable/disable vsync functionality based on some system information (e.g. hardcoded if vendor == Chromium). Or maybe trap the error instead and use this as an indicator that vsync functionality is not supported on the platform.

b

Notes on converting loaded image surfaces to textures

Getting texture loading right.

  • supported pixelformats -> textures
  • unsupported pixelformats should barf an error
  • flipping if needed (not sure if it can be determined from surface data though)

Pixel data types we need to care about: GL_RGB, GL_BGR, GL_RGBA, GL_BGRA. Internal data types we need to care about: GL_RGB, GL_RGBA, and maybe GL_SRGB8 and GL_SRGB8_ALPHA_8

SDL_ConvertSurface (or rather, SDL_ConvertSurfaceFormat) could be useful to get it into a proper format if it isn’t in one. Actually, unless we hit performance limit, this would be preferred solution because of simplicity. (setf use-alpha (ispixelformat-alpha surface->format))

(if (or (ispixelformat-indexed surface->format) (and (not (= (bytes-per-pixel surface->format 3))) (not (= (bytes-per-pixel surface->format 4))) (convert-surface surface use-alpha) (return (if use-alpha :rgba :rgb)))

;; TODO handling of RGB / BGR and RGBA / BGRA has-blue-channel-first –> BGR24 | BGR888 | BGRA8888

NOTE: X86 is little-endian, FWIW.

Right now we went the lazy way and did a force-convert with SDL_ConvertSurfaceFormat. Would be cool to replace it in the future because of performance and memory reasons.

Piece of log from testing SDL image surface loading

<DEBUG> [00:42:34] p2d filegNgwby (load-debug-images-and-dump-info load-img-and-dump-info) - PARENDECK2D::IMG: “assets/trc_tex.gif” (SDL2:SURFACE-WIDTH PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-HEIGHT PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-PITCH PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-FORMAT PARENDECK2D::LOADED-IMG): #.(SB-SYS:INT-SAP #X7FFFDC3B8A10) (SDL2:SURFACE-FORMAT-FORMAT PARENDECK2D::LOADED-IMG): :INDEX8

<DEBUG> [00:42:34] p2d filegNgwby (load-debug-images-and-dump-info load-img-and-dump-info) - PARENDECK2D::IMG: “assets/trc_tex.jpg” (SDL2:SURFACE-WIDTH PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-HEIGHT PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-PITCH PARENDECK2D::LOADED-IMG): 384 (SDL2:SURFACE-FORMAT PARENDECK2D::LOADED-IMG): #.(SB-SYS:INT-SAP #X7FFFDC3C68E0) (SDL2:SURFACE-FORMAT-FORMAT PARENDECK2D::LOADED-IMG): :RGB24

<DEBUG> [00:42:34] p2d filegNgwby (load-debug-images-and-dump-info load-img-and-dump-info) - PARENDECK2D::IMG: “assets/trc_tex.tga” (SDL2:SURFACE-WIDTH PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-HEIGHT PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-PITCH PARENDECK2D::LOADED-IMG): 512 (SDL2:SURFACE-FORMAT PARENDECK2D::LOADED-IMG): #.(SB-SYS:INT-SAP #X7FFFDC42D870) (SDL2:SURFACE-FORMAT-FORMAT PARENDECK2D::LOADED-IMG): :ARGB8888

<DEBUG> [00:42:34] p2d filegNgwby (load-debug-images-and-dump-info load-img-and-dump-info) - PARENDECK2D::IMG: “assets/trc_tex_24bit.bmp” (SDL2:SURFACE-WIDTH PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-HEIGHT PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-PITCH PARENDECK2D::LOADED-IMG): 384 (SDL2:SURFACE-FORMAT PARENDECK2D::LOADED-IMG): #.(SB-SYS:INT-SAP #X7FFFDC3B8A10) (SDL2:SURFACE-FORMAT-FORMAT PARENDECK2D::LOADED-IMG): :BGR24

<DEBUG> [00:42:34] p2d filegNgwby (load-debug-images-and-dump-info load-img-and-dump-info) - PARENDECK2D::IMG: “assets/trc_tex_32bit_argb.bmp” (SDL2:SURFACE-WIDTH PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-HEIGHT PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-PITCH PARENDECK2D::LOADED-IMG): 512 (SDL2:SURFACE-FORMAT PARENDECK2D::LOADED-IMG): #.(SB-SYS:INT-SAP #X7FFFDC3B8A10) (SDL2:SURFACE-FORMAT-FORMAT PARENDECK2D::LOADED-IMG): :RGBA8888

<DEBUG> [00:42:34] p2d filegNgwby (load-debug-images-and-dump-info load-img-and-dump-info) - PARENDECK2D::IMG: “assets/trc_tex_32bit_xrgb.bmp” (SDL2:SURFACE-WIDTH PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-HEIGHT PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-PITCH PARENDECK2D::LOADED-IMG): 512 (SDL2:SURFACE-FORMAT PARENDECK2D::LOADED-IMG): #.(SB-SYS:INT-SAP #X7FFFDC3B8A10) (SDL2:SURFACE-FORMAT-FORMAT PARENDECK2D::LOADED-IMG): :RGBX8888

<DEBUG> [00:42:34] p2d filegNgwby (load-debug-images-and-dump-info load-img-and-dump-info) - PARENDECK2D::IMG: “assets/trc_tex_24bit.png” (SDL2:SURFACE-WIDTH PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-HEIGHT PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-PITCH PARENDECK2D::LOADED-IMG): 384 (SDL2:SURFACE-FORMAT PARENDECK2D::LOADED-IMG): #.(SB-SYS:INT-SAP #X7FFFDC3D8390) (SDL2:SURFACE-FORMAT-FORMAT PARENDECK2D::LOADED-IMG): :RGB24

<DEBUG> [00:42:34] p2d filegNgwby (load-debug-images-and-dump-info load-img-and-dump-info) - PARENDECK2D::IMG: “assets/trc_tex_32bit.png” (SDL2:SURFACE-WIDTH PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-HEIGHT PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-PITCH PARENDECK2D::LOADED-IMG): 512 (SDL2:SURFACE-FORMAT PARENDECK2D::LOADED-IMG): #.(SB-SYS:INT-SAP #X7FFFDC3FD0E0) (SDL2:SURFACE-FORMAT-FORMAT PARENDECK2D::LOADED-IMG): :ABGR8888

<DEBUG> [00:42:34] p2d filegNgwby (load-debug-images-and-dump-info load-img-and-dump-info) - PARENDECK2D::IMG: “assets/trc_tex_lzw.tiff” (SDL2:SURFACE-WIDTH PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-HEIGHT PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-PITCH PARENDECK2D::LOADED-IMG): 512 (SDL2:SURFACE-FORMAT PARENDECK2D::LOADED-IMG): #.(SB-SYS:INT-SAP #X7FFFDC3FD0E0) (SDL2:SURFACE-FORMAT-FORMAT PARENDECK2D::LOADED-IMG): :ABGR8888

<DEBUG> [00:42:34] p2d filegNgwby (load-debug-images-and-dump-info load-img-and-dump-info) - PARENDECK2D::IMG: “assets/trc_tex_packed.tiff” (SDL2:SURFACE-WIDTH PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-HEIGHT PARENDECK2D::LOADED-IMG): 128 (SDL2:SURFACE-PITCH PARENDECK2D::LOADED-IMG): 512 (SDL2:SURFACE-FORMAT PARENDECK2D::LOADED-IMG): #.(SB-SYS:INT-SAP #X7FFFDC3FD0E0) (SDL2:SURFACE-FORMAT-FORMAT PARENDECK2D::LOADED-IMG): :ABGR8888

Free enough fonts to use

Surprisingly difficult to find.

Inlining

Important observation - if you change an inlined function, best recompile the whole project; otherwise, Quicklisp / ASDF won’t pick it up by itself and you may end up with outdated code here and there, doing weird stuff.

Recompilation

(asdf:load-system :parendeck2d :force t :force-not t)