mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-07 08:08:49 +00:00
somehwat of a success... but bloated.
This commit is contained in:
@@ -0,0 +1,461 @@
|
|||||||
|
/*
|
||||||
|
* tape_atom_dsl.h
|
||||||
|
* ============================================================================
|
||||||
|
*
|
||||||
|
* TAPE ATOM DSL — annotation layer for tape atoms (lottes_tape.h).
|
||||||
|
*
|
||||||
|
* This header turns `__attribute__((annotate(...)))` and `_Pragma(...)` into
|
||||||
|
* a small named DSL that the metaprogram can validate against.
|
||||||
|
*
|
||||||
|
* The C compiler treats every macro below as a no-op:
|
||||||
|
* - atom_init / atom_terminate / atom_bind / atom_setup / atom_commit /
|
||||||
|
* atom_annot all expand to `__attribute__((annotate("..."))) MipsAtom_(name)`
|
||||||
|
* — accepted by GCC (with -Wno-attributes), absent at runtime.
|
||||||
|
* - atom_resource / atom_region / atom_group / atom_cadence / atom_async
|
||||||
|
* expand to `_Pragma("...")` — accepted by any C11 preprocessor.
|
||||||
|
*
|
||||||
|
* The metaprogram (tape_atom_annotation_pass.lua) reads the source-as-written
|
||||||
|
* and validates:
|
||||||
|
* - every MipsAtom_ has one atom_*() annotation (no orphans)
|
||||||
|
* - phase is recognized (init/bind/setup/work/commit/terminate)
|
||||||
|
* - reads/writes reference canonical wave-context registers
|
||||||
|
* - rbind atoms reference a real Binds_* struct declaration
|
||||||
|
* - word-counts in tapre metadata agree with the body's actual .word count
|
||||||
|
* - resource/region/group/cadence/async pragmas are spelled correctly and
|
||||||
|
* reference known enum values
|
||||||
|
*
|
||||||
|
* ============================================================================
|
||||||
|
*
|
||||||
|
* PUTTING IT ON AN ATOM — the canonical pattern
|
||||||
|
*
|
||||||
|
* _tape_resources_
|
||||||
|
* atom_resource(cube_tri, "model_ship_cube")
|
||||||
|
* atom_region (cube_tri, PRIM_ARENA)
|
||||||
|
* atom_group (cube_tri, GROUP_RENDER_PRIMS)
|
||||||
|
* atom_cadence (cube_tri, CADENCE_FRAME)
|
||||||
|
*
|
||||||
|
* atom_annot(cube_tri, phase_work,
|
||||||
|
* tape_regs(R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase),
|
||||||
|
* tape_regs(R_PrimCursor, R_FaceCursor))
|
||||||
|
* internal MipsAtom_(cube_tri) {
|
||||||
|
* atom_label(culling),
|
||||||
|
* // ... atom body ...
|
||||||
|
* atom_label(bounds_chk),
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* atom_offset(culling, bounds_chk) // ← branch target, validated
|
||||||
|
*
|
||||||
|
* RBIND pattern — `Binds_*` is the contract
|
||||||
|
*
|
||||||
|
* // Wave-context register layout (declarative):
|
||||||
|
* typedef struct Binds_TrackFaceBatch {
|
||||||
|
* U4 R_PrimCursor, R_FaceCursor,
|
||||||
|
* R_VertBase, R_OtBase;
|
||||||
|
* } Binds_TrackFaceBatch;
|
||||||
|
*
|
||||||
|
* atom_resource(rbind_track_face_batch, "track_face_batch_42")
|
||||||
|
* atom_region (rbind_track_face_batch, HEAP_3D)
|
||||||
|
* atom_group (rbind_track_face_batch, GROUP_LOAD_FACES)
|
||||||
|
* atom_cadence (rbind_track_face_batch, CADENCE_ONDEMAND)
|
||||||
|
* atom_async (rbind_track_face_batch, true)
|
||||||
|
*
|
||||||
|
* atom_bind(rbind_track_face_batch, Binds_TrackFaceBatch,
|
||||||
|
* tape_regs(R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase))
|
||||||
|
* internal MipsAtom_(rbind_track_face_batch) { ... };
|
||||||
|
*
|
||||||
|
* Annotation rules
|
||||||
|
* ----------------
|
||||||
|
* 1. Each MipsAtom_(name) needs EXACTLY ONE atom_*() macro on the line
|
||||||
|
* immediately above. No annotation = orphan (warning). Two annotations
|
||||||
|
* on the same name = duplicate (error).
|
||||||
|
*
|
||||||
|
* 2. atom_init and atom_terminate take only the name.
|
||||||
|
*
|
||||||
|
* 3. atom_setup and atom_commit take name + reads.
|
||||||
|
*
|
||||||
|
* 4. atom_bind takes name + Binds_* type + writes.
|
||||||
|
*
|
||||||
|
* 5. atom_annot takes name + phase token + reads + writes.
|
||||||
|
* Phase tokens: phase_init / phase_bind / phase_setup / phase_work /
|
||||||
|
* phase_commit / phase_terminate.
|
||||||
|
*
|
||||||
|
* 6. Optional pragmas (atom_resource / atom_region / atom_group /
|
||||||
|
* atom_cadence / atom_async) attach metadata to the atom. They can
|
||||||
|
* appear in any order, with one per atom. They're independent of the
|
||||||
|
* atom_*() macro — multiple pragmatics are fine.
|
||||||
|
*
|
||||||
|
* ============================================================================
|
||||||
|
*
|
||||||
|
* WHY A SEPARATE LAYER (not just put everything in source comments)?
|
||||||
|
*
|
||||||
|
* Source comments are invisible to the compiler. Annotations live in the
|
||||||
|
* source as actual C tokens, so:
|
||||||
|
* - they can never silently get out of sync with the code (the build
|
||||||
|
* fails at preprocessing if the metaprogram disagrees)
|
||||||
|
* - they can be cross-validated against metadata (build fails if a
|
||||||
|
* WORD_COUNT entry drifts away from the .word count in source)
|
||||||
|
* - they make the C compiler a witness ("there's a marker here, and
|
||||||
|
* it's labelled, and it has arguments") without making the C compile
|
||||||
|
* itself do any work
|
||||||
|
*
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef INTELLISENSE_DIRECTIVES
|
||||||
|
#pragma once
|
||||||
|
// #include <stdint.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* PHASE TOKENS — strings, used as the second arg to atom_annot(...)
|
||||||
|
*
|
||||||
|
* Why strings? They preserve the metaprogram's ability to read phase directly
|
||||||
|
* from the source-as-written, even when the macro isn't expanded. The Lua
|
||||||
|
* tool also has a MACRO_EXPANSION table for resolving phase_* source-level
|
||||||
|
* references.
|
||||||
|
*
|
||||||
|
* atom_annot(cube_tri, phase_work, ...) ← legal
|
||||||
|
* atom_annot(cube_tri, "work", ...) ← legal (and equivalent)
|
||||||
|
* atom_annot(cube_tri, phase_setup, ...) ← legal
|
||||||
|
*
|
||||||
|
* ============================================================================*/
|
||||||
|
|
||||||
|
#define phase_init "init"
|
||||||
|
#define phase_bind "bind"
|
||||||
|
#define phase_setup "setup"
|
||||||
|
#define phase_work "work"
|
||||||
|
#define phase_commit "commit"
|
||||||
|
#define phase_terminate "terminate"
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* WAVE-CONTEXT REGISTERS — canonical register set for the tape wave model.
|
||||||
|
*
|
||||||
|
* The tape-atom runtime carries four registers across a wave:
|
||||||
|
*
|
||||||
|
* R_PrimCursor output pointer into the prim arena (next OT entry to write)
|
||||||
|
* R_FaceCursor input pointer into the face array (next face to consume)
|
||||||
|
* R_VertBase base pointer into the vertex arena (this wave's vertices)
|
||||||
|
* R_OtBase base pointer into the ordering table (this wave's OT slot)
|
||||||
|
*
|
||||||
|
* Each atom declares its reads/writes against this canonical set. The Lua
|
||||||
|
* tool rejects wave-context positions that reference any other register
|
||||||
|
* (warning today — the C compiler's R_T4..R_T7 / R_RA / etc. aliases are
|
||||||
|
* implementation details and not part of the typed surface).
|
||||||
|
*
|
||||||
|
* If your atom needs to touch GTE / SP / DMA / other side state, declare it
|
||||||
|
* at the source level as you normally would — but DO NOT put those registers
|
||||||
|
* in tape_regs(...). Wave-context is a closed set.
|
||||||
|
*
|
||||||
|
* ============================================================================*/
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* REGION TOKENS — memory regions atoms may allocate from or write into.
|
||||||
|
*
|
||||||
|
* Use atom_region(name, REGION) to declare. The Lua tool validates that the
|
||||||
|
* region is in this set, AND that:
|
||||||
|
* - rbind atoms declare the source region (usually HEAP_3D or CDROM_STREAM)
|
||||||
|
* - work atoms declare the destination region (the arena they push to)
|
||||||
|
* - commit atoms must declare a region equal to what setup wrote, so the
|
||||||
|
* C-side mirror is consistent
|
||||||
|
*
|
||||||
|
* Add new regions by extending this list and the metaprogram's KNOWN_REGIONS.
|
||||||
|
* Don't add regions ad-hoc — every new region becomes part of the contract.
|
||||||
|
*
|
||||||
|
* ============================================================================*/
|
||||||
|
#define REGION_PRIM_ARENA prim_arena /* OT/prim packet arena */
|
||||||
|
#define REGION_FACE_ARENA face_arena /* face index array */
|
||||||
|
#define REGION_VERTEX_ARENA vertex_arena /* vertex pool */
|
||||||
|
#define REGION_OT_ARENA ot_arena /* ordering-table array */
|
||||||
|
#define REGION_HEAP_3D heap_3d_models /* loaded model heap */
|
||||||
|
#define REGION_CDROM_STREAM cdrom_stream /* CDROM read buffer */
|
||||||
|
#define REGION_VRAM vram_heap /* VRAM texture/GPU buffer */
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* CADENCE TOKENS — how often the atom runs.
|
||||||
|
*
|
||||||
|
* frame runs every vsync (rendering, input poll)
|
||||||
|
* once runs exactly once per process lifetime (init, terminate)
|
||||||
|
* ondemand runs when triggered by event (CDROM load, async DMA complete)
|
||||||
|
*
|
||||||
|
* Used as a hint for the metaprogram to flag:
|
||||||
|
* - frame-cadence atoms that have side effects (they'll be hit many times,
|
||||||
|
* so avoid global state mutation unless it's idempotent)
|
||||||
|
* - once-cadence atoms inside "if (frame_count == 0)" guards (the guard
|
||||||
|
* is then provably one-shot, the metaprogram can lift initialization)
|
||||||
|
* - ondemand atoms that are missed by the wave scheduler (forces async
|
||||||
|
* and discards yield results without further processing)
|
||||||
|
*
|
||||||
|
* ============================================================================*/
|
||||||
|
#define CADENCE_FRAME frame
|
||||||
|
#define CADENCE_ONCE once
|
||||||
|
#define CADENCE_ONDEMAND ondemand
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* tape_regs(...) — wave-context register list
|
||||||
|
*
|
||||||
|
* tape_regs(R_PrimCursor, R_FaceCursor) → (R_PrimCursor, R_FaceCursor)
|
||||||
|
*
|
||||||
|
* The macro produces a comma-evaluated expression that the C compiler
|
||||||
|
* silently discards (it's wrapped in parentheses in the call argument
|
||||||
|
* position — the result is never bound). The Lua tool pattern-matches the
|
||||||
|
* "tape_regs(...)" token to extract the list.
|
||||||
|
*
|
||||||
|
* You can have at most one tape_regs(...) in the reads slot and one in the
|
||||||
|
* writes slot of atom_annot. To declare multiple disjoint sets (rare), just
|
||||||
|
* declare the union — the metaprogram doesn't track which reads need which
|
||||||
|
* writes at this granularity.
|
||||||
|
*
|
||||||
|
* ============================================================================*/
|
||||||
|
#define tape_regs(...) (__VA_ARGS__)
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* ATOM ANNOTATION MACROS
|
||||||
|
*
|
||||||
|
* Each expands to `__attribute__((annotate("kind"))) MipsAtom_(name)` —
|
||||||
|
* the GCC attribute is accepted under -Wno-attributes (already in your
|
||||||
|
* build flags) and stripped at runtime. The annotation string is just the
|
||||||
|
* macro kind ("atom_annot", "atom_bind", etc.) — the metaprogram reads
|
||||||
|
* the macro call's full args list from the source-as-written.
|
||||||
|
*
|
||||||
|
* ============================================================================*/
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* atom_init — entry into tape_runtime_main
|
||||||
|
*
|
||||||
|
* atom_init(tape_main)
|
||||||
|
* internal MipsAtom_(tape_main) { ... };
|
||||||
|
*
|
||||||
|
* Implies: no reads, no writes (wave-context not established yet).
|
||||||
|
* ----------------------------------------------------------------------------*/
|
||||||
|
#define atom_init(name) __attribute__((annotate("atom_init")))
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* atom_terminate — exit from tape_runtime_main
|
||||||
|
*
|
||||||
|
* atom_terminate(tape_exit)
|
||||||
|
* internal MipsAtom_(tape_exit) { ... };
|
||||||
|
*
|
||||||
|
* Implies: no reads, no writes (wave-context destroyed at this point).
|
||||||
|
* ----------------------------------------------------------------------------*/
|
||||||
|
#define atom_terminate(name) __attribute__((annotate("atom_terminate")))
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* atom_setup — pre-work atom: prepares engine state (e.g., set_gte_world)
|
||||||
|
*
|
||||||
|
* atom_setup(set_gte_world, tape_regs(R_TapePtr))
|
||||||
|
* internal MipsAtom_(set_gte_world) { ... };
|
||||||
|
*
|
||||||
|
* Reads: anything (the engine state you're reading)
|
||||||
|
* Writes: engine state (GTE / DMA / etc. — declared in source, not part of
|
||||||
|
* wave-context, so doesn't go in tape_regs)
|
||||||
|
*
|
||||||
|
* The metaprogram checks that setup is followed (in atomic order) by a work
|
||||||
|
* atom in the same wave — there's no point in setting up state if no one
|
||||||
|
* reads it.
|
||||||
|
* ----------------------------------------------------------------------------*/
|
||||||
|
#define atom_setup(name, reads) __attribute__((annotate("atom_setup")))
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* atom_commit — post-work atom: flushes wave-context back to C-side state
|
||||||
|
*
|
||||||
|
* atom_commit(sync_prim_cursor, tape_regs(R_PrimCursor))
|
||||||
|
* internal MipsAtom_(sync_prim_cursor) { ... };
|
||||||
|
*
|
||||||
|
* Reads: wave-context registers (the ones you sync back to C)
|
||||||
|
* Writes: C-side mirror (declared in source — not part of wave-context)
|
||||||
|
*
|
||||||
|
* The metaprogram checks that commit is preceded (in atomic order) by a
|
||||||
|
* work atom that wrote the registers this commit is reading.
|
||||||
|
* ----------------------------------------------------------------------------*/
|
||||||
|
#define atom_commit(name, reads) __attribute__((annotate("atom_commit")))
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* atom_bind — rbind atom: read wave-context registers from tape pointer
|
||||||
|
*
|
||||||
|
* atom_bind(rbind_cube_tri, Binds_CubeTri,
|
||||||
|
* tape_regs(R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase))
|
||||||
|
* internal MipsAtom_(rbind_cube_tri) { ... };
|
||||||
|
*
|
||||||
|
* The binds_struct MUST be a typedef'd type (declared via
|
||||||
|
* `typedef struct Binds_X { ... } Binds_X;` somewhere in the source).
|
||||||
|
* The Lua tool cross-references this. Missing struct = error.
|
||||||
|
*
|
||||||
|
* Implicit: reads R_TapePtr, writes the four wave-context registers.
|
||||||
|
* ----------------------------------------------------------------------------*/
|
||||||
|
#define atom_bind(name, binds_struct, writes) __attribute__((annotate("atom_bind")))
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* atom_annot — generic work atom with explicit phase
|
||||||
|
*
|
||||||
|
* atom_annot(cube_tri, phase_work,
|
||||||
|
* tape_regs(R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase),
|
||||||
|
* tape_regs(R_PrimCursor, R_FaceCursor))
|
||||||
|
* internal MipsAtom_(cube_tri) { ... };
|
||||||
|
*
|
||||||
|
* Use this for the bulk of your atoms. For init/setup/commit/bind, prefer
|
||||||
|
* the convenience macros above — they pin the phase for you.
|
||||||
|
*
|
||||||
|
* The phase arg is one of: phase_init / phase_bind / phase_setup /
|
||||||
|
* phase_work / phase_commit / phase_terminate. Spelling mistakes are errors.
|
||||||
|
* ----------------------------------------------------------------------------*/
|
||||||
|
#define atom_annot(name, phase, reads, writes) __attribute__((annotate("atom_annot")))
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* RESOURCE / GROUP / CADENCE / REGION / ASYNC — optional atom metadata
|
||||||
|
*
|
||||||
|
* These don't annotate the atom semantically (phase/reads/writes do that).
|
||||||
|
* They attach extra context that the metaprogram uses to catch:
|
||||||
|
* - same resource loaded twice in different ways
|
||||||
|
* - atoms that span multiple regions (likely bug — pick one)
|
||||||
|
* - frame-cadence atoms that should be once-cadence (perf / correctness)
|
||||||
|
* - ondemand atoms that aren't async (CDROM races)
|
||||||
|
*
|
||||||
|
* You can use as many as apply to a given atom, in any order, immediately
|
||||||
|
* above the atom_*() macro.
|
||||||
|
*
|
||||||
|
* ============================================================================*/
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* atom_resource — name the logical resource the atom references
|
||||||
|
*
|
||||||
|
* atom_resource(cube_tri, "model_ship_cube")
|
||||||
|
* atom_resource(load_track_faces, "track_lavender_field_0x42")
|
||||||
|
* atom_resource(play_engine_sfx, "sfx_engine_loop")
|
||||||
|
*
|
||||||
|
* Use any human-readable string. The metaprogram:
|
||||||
|
* - validates resource strings are non-empty and don't contain control chars
|
||||||
|
* - flags duplicates across atoms with the same name (two atoms claiming
|
||||||
|
* ownership of a resource is usually a refactor artifact or bug)
|
||||||
|
* - flags references to resources that no atom actually defines
|
||||||
|
*
|
||||||
|
* The arg is a STRING LITERAL, so it can't accidentally alias a variable.
|
||||||
|
* ----------------------------------------------------------------------------*/
|
||||||
|
#define atom_resource(name, res_id) //_Pragma("atom " #name " resource=" res_id)
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* atom_region — name the memory region the atom touches
|
||||||
|
*
|
||||||
|
* atom_region(cube_tri, REGION_PRIM_ARENA)
|
||||||
|
* atom_region(load_faces, REGION_HEAP_3D)
|
||||||
|
* atom_region(load_tex, REGION_VRAM)
|
||||||
|
*
|
||||||
|
* Use REGION_* tokens above. The metaprogram enforces the closed set.
|
||||||
|
*
|
||||||
|
* Edge cases the metaprogram catches:
|
||||||
|
* - rbind atom that doesn't declare a SOURCE region (where is it loading from?)
|
||||||
|
* - work atom with no destination region (where is it pushing to?)
|
||||||
|
* - region that disagrees with the Binds_* struct layout (you said it's a
|
||||||
|
* prim_arena rbind but the struct has 4 faces in it — wait, that's wrong)
|
||||||
|
* ----------------------------------------------------------------------------*/
|
||||||
|
#define atom_region(name, region) //_Pragma("atom " #name " region=" #region)
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* atom_group — bundle atoms into a logical batch (track-load, sound-load, etc.)
|
||||||
|
*
|
||||||
|
* atom_group(load_track_face_42, GROUP_LOAD_FACES)
|
||||||
|
* atom_group(load_track_face_43, GROUP_LOAD_FACES)
|
||||||
|
* atom_group(swap_face_42_43, GROUP_VISIBILITY_SWAP)
|
||||||
|
*
|
||||||
|
* Use any token as the group id. The metaprogram:
|
||||||
|
* - validates all atoms in a group emit their waves in the same tb_group
|
||||||
|
* (no spawning other waves inside a group)
|
||||||
|
* - flags groups with only one member (probably a typo — meant to be a group?)
|
||||||
|
* - validates cross-group edges (no atom reads what another group writes,
|
||||||
|
* unless explicitly grouped together)
|
||||||
|
*
|
||||||
|
* Useful when:
|
||||||
|
* - subdivisible work (track-face batches, polygon subdivision) needs to
|
||||||
|
* confirm that all batches of one logical visible scene are emitted
|
||||||
|
* together
|
||||||
|
* - async loads (CDROM -> VRAM) need to be grouped so all batches complete
|
||||||
|
* before the swap
|
||||||
|
*
|
||||||
|
* Use GROUPS for sound effects to track which sound plays during which atom,
|
||||||
|
* which is needed if the sound tool ever has to validate "this atom is the
|
||||||
|
* trigger for an audio play".
|
||||||
|
* ----------------------------------------------------------------------------*/
|
||||||
|
#define atom_group(name, group_id) //_Pragma("atom " #name " group=" #group_id)
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* atom_cadence — declare execution frequency
|
||||||
|
*
|
||||||
|
* atom_cadence(render_frame, CADENCE_FRAME) // every vsync
|
||||||
|
* atom_cadence(load_track_faces, CADENCE_ONDEMAND) // on demand
|
||||||
|
* atom_cadence(init_heap, CADENCE_ONCE) // process lifetime
|
||||||
|
*
|
||||||
|
* Default (no atom_cadence call) is CADENCE_FRAME — most atoms run every
|
||||||
|
* frame. Override explicitly when not.
|
||||||
|
*
|
||||||
|
* The metaprogram's checks:
|
||||||
|
* - CADENCE_ONCE atoms inside `if (frame == 0)` or `if (!initialized)` are
|
||||||
|
* tagged, validating that guards are required (or warning if missing)
|
||||||
|
* - CADENCE_FRAME atoms that mutate state outside the wave context get
|
||||||
|
* flagged (likely a bug — state should persist through commits)
|
||||||
|
* - CADENCE_ONDEMAND atoms must have atom_async — otherwise the trigger
|
||||||
|
* mechanism is undefined
|
||||||
|
* ----------------------------------------------------------------------------*/
|
||||||
|
#define atom_cadence(name, cadence) //_Pragma("atom " #name " cadence=" #cadence)
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* atom_async — declare whether the atom yields / interacts with CDROM DMA
|
||||||
|
*
|
||||||
|
* atom_async(load_track_tex, true) // CDROM read yield
|
||||||
|
* atom_async(load_vram, true) // VRAM upload DMA
|
||||||
|
* atom_async(render_frame, false) // pure compute, no async
|
||||||
|
*
|
||||||
|
* The metaprogram requires this for CADENCE_ONDEMAND atoms. For
|
||||||
|
* CADENCE_FRAME, it's optional but documents intent.
|
||||||
|
*
|
||||||
|
* Note: CDROM ATOMS in Psy-Q are typically implemented as a chain of
|
||||||
|
* "async-init" atom followed by a "wait-for-completion" atom. Both atoms
|
||||||
|
* should be marked async=true, and both should have the same resource/group
|
||||||
|
* tag (so the metaprogram can verify they're paired).
|
||||||
|
* ----------------------------------------------------------------------------*/
|
||||||
|
#define atom_async(name, is_async) //_Pragma("atom " #name " async=" #is_async)
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* WORD-COUNT ANNOTATION FOR A #define MAC
|
||||||
|
*
|
||||||
|
* tape_words(mac_yield, 1)
|
||||||
|
* #define mac_yield() \
|
||||||
|
* load_word(R_AtomJmp, R_TapePtr, 0), \
|
||||||
|
* add_ui_1(R_TapePtr, 4), \
|
||||||
|
* jump_reg(R_AtomJmp), \
|
||||||
|
* nop
|
||||||
|
*
|
||||||
|
* The compiler accepts the unknown _Pragma. The Lua tool reads it and
|
||||||
|
* cross-checks against WORD_COUNT(mac_yield, 1) in tape_atom.metadata.h.
|
||||||
|
* If they disagree, build fails.
|
||||||
|
*
|
||||||
|
* Use sparingly — only on multi-word macros (single-word ones don't need
|
||||||
|
* drift tracking; they're checked by the .word-count pass anyway).
|
||||||
|
*
|
||||||
|
* ============================================================================*/
|
||||||
|
#define tape_words(name, n) //_Pragma(#name " tape_atom words=" #n)
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* atom_label / atom_offset — branch target machinery
|
||||||
|
*
|
||||||
|
* atom_label(culling) ← nothing in C; anchor only
|
||||||
|
* ... body ...
|
||||||
|
* atom_label(bounds_chk) ← another anchor
|
||||||
|
*
|
||||||
|
* atom_offset(culling, bounds_chk) ← resolved by gen/.offsets.h
|
||||||
|
*
|
||||||
|
* The metaprogram generates gen/atom_offsets.h with one
|
||||||
|
* #define atom_offset__culling__bounds_chk ((target - branch_pos - 1))
|
||||||
|
* per atom_offset(F, T) call. The preprocessor then expands your call to
|
||||||
|
* the right immediate value.
|
||||||
|
*
|
||||||
|
* If gen/atom_offsets.h is stale (or atom_label(name) is undefined),
|
||||||
|
* `atom_offset__F__T` becomes an undefined macro and the C build fails.
|
||||||
|
* This catches:
|
||||||
|
* - typo in atom_label (no anchor → metaprogram doesn't emit the macro)
|
||||||
|
* - .offsets.h not regenerated after body edits
|
||||||
|
* - body edit that broke the offset math (recompile + retest picks it up
|
||||||
|
* in CPU emulator)
|
||||||
|
*
|
||||||
|
* ============================================================================*/
|
||||||
|
|
||||||
|
#define atom_offset(F, T) atom_offset_ ## F ## _ ## T
|
||||||
|
#define atom_label(name) /* anchor — see metaprogram documentation */
|
||||||
@@ -4,9 +4,6 @@
|
|||||||
|
|
||||||
#pragma region lottes_tape
|
#pragma region lottes_tape
|
||||||
|
|
||||||
// Dispatch macro: token-pastes <tag>_<target> to the enum name
|
|
||||||
#undef atom_offset
|
|
||||||
#define atom_offset(tag, name) atom_offset_##tag##_##name
|
|
||||||
|
|
||||||
#pragma endregion lottes_tape
|
#pragma endregion lottes_tape
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
# include "mips.h"
|
# include "mips.h"
|
||||||
# include "gte.h"
|
# include "gte.h"
|
||||||
# include "memory.h"
|
# include "memory.h"
|
||||||
|
# include "atom_dsl.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef U4 const MipsCode;
|
typedef U4 const MipsCode;
|
||||||
@@ -20,16 +21,19 @@ typedef U4 const MipsCode;
|
|||||||
enum {
|
enum {
|
||||||
R_AtomJmp = R_T9,
|
R_AtomJmp = R_T9,
|
||||||
R_TapePtr = R_T8, /* The Instruction Stream Pointer */
|
R_TapePtr = R_T8, /* The Instruction Stream Pointer */
|
||||||
|
R_InCursor = R_T4, /* Input data cursor */
|
||||||
|
|
||||||
R_PrimCursor = R_T7, /* VRAM output cursor (primitive buffer) */
|
R_PrimCursor = R_T7, /* VRAM output cursor (primitive buffer) */
|
||||||
R_FaceCursor = R_T4, /* Input data cursor (indices/faces) */
|
R_FaceCursor = R_T4, /* Input data cursor (indices/faces) */
|
||||||
R_InCursor = R_T4, /* Input data cursor (indices/faces) */
|
|
||||||
R_VertBase = R_T5, /* Base address of the vertex array */
|
R_VertBase = R_T5, /* Base address of the vertex array */
|
||||||
R_OtBase = R_T6, /* Base address of the Ordering Table */
|
R_OtBase = R_T6, /* Base address of the Ordering Table */
|
||||||
|
|
||||||
/* Stringification codes for the GCC inline assembler clobber lists */
|
/* Stringification codes for the GCC inline assembler clobber lists */
|
||||||
#define R_TapePtr_Code R_T8_Code
|
#define R_TapePtr_Code R_T8_Code
|
||||||
|
#define R_InCursor_Code R_T4_Code
|
||||||
|
|
||||||
#define R_PrimCursor_Code R_T7_Code
|
#define R_PrimCursor_Code R_T7_Code
|
||||||
#define R_FaceCursor_Code R_T4_Code
|
#define R_FaceCursor_Code R_T4_Code
|
||||||
#define R_InCursor_Code R_T4_Code
|
|
||||||
#define R_VertBase_Code R_T5_Code
|
#define R_VertBase_Code R_T5_Code
|
||||||
#define R_OtBase_Code R_T6_Code
|
#define R_OtBase_Code R_T6_Code
|
||||||
};
|
};
|
||||||
@@ -274,7 +278,15 @@ internal MipsAtom_(rbind_cube_tri) {
|
|||||||
* Inner branch (OTZ bounds): branch_equal(R_AT, R_0, 13)
|
* Inner branch (OTZ bounds): branch_equal(R_AT, R_0, 13)
|
||||||
* → Skip 13 instructions from BD slot, land at add_ui(R_FaceCur,...)
|
* → Skip 13 instructions from BD slot, land at add_ui(R_FaceCur,...)
|
||||||
* ============================================================================ */
|
* ============================================================================ */
|
||||||
internal MipsAtom_(cube_tri) {
|
atom_resource(cube_tri, "model_ship_cube")
|
||||||
|
atom_region (cube_tri, REGION_PRIM_ARENA)
|
||||||
|
atom_group (cube_tri, GROUP_RENDER_PRIMS)
|
||||||
|
atom_cadence (cube_tri, CADENCE_FRAME)
|
||||||
|
atom_annot(cube_tri, phase_work,
|
||||||
|
tape_regs(R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase),
|
||||||
|
tape_regs(R_PrimCursor, R_FaceCursor))
|
||||||
|
internal
|
||||||
|
MipsAtom_(cube_tri) {
|
||||||
/* ── 1. Load 4 face indices from R_FaceCur ──────────────────────────── */
|
/* ── 1. Load 4 face indices from R_FaceCur ──────────────────────────── */
|
||||||
load_half_u(R_T0, R_FaceCursor, 0), /* T0 = face->x (vertex 0 index) */
|
load_half_u(R_T0, R_FaceCursor, 0), /* T0 = face->x (vertex 0 index) */
|
||||||
load_half_u(R_T1, R_FaceCursor, 2), /* T1 = face->y (vertex 1 index) */
|
load_half_u(R_T1, R_FaceCursor, 2), /* T1 = face->y (vertex 1 index) */
|
||||||
|
|||||||
@@ -4,9 +4,6 @@
|
|||||||
|
|
||||||
#pragma region hello_gte_tape
|
#pragma region hello_gte_tape
|
||||||
|
|
||||||
// Dispatch macro: token-pastes <tag>_<target> to the enum name
|
|
||||||
#undef atom_offset
|
|
||||||
#define atom_offset(tag, name) atom_offset_##tag##_##name
|
|
||||||
|
|
||||||
// --- atom: floor_tri (32 words) ---
|
// --- atom: floor_tri (32 words) ---
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "duffle/gte.h"
|
#include "duffle/gte.h"
|
||||||
|
|
||||||
# include "duffle/gen/lottes_tape.offsets.h"
|
# include "duffle/gen/lottes_tape.offsets.h"
|
||||||
|
#include "duffle/atom_dsl.h"
|
||||||
#include "duffle/lottes_tape.h"
|
#include "duffle/lottes_tape.h"
|
||||||
|
|
||||||
# include "tape_atom.metadata.h"
|
# include "tape_atom.metadata.h"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#ifdef INTELLISENSE_DIRECTIVES
|
#ifdef INTELLISENSE_DIRECTIVES
|
||||||
# include "duffle/lottes_tape.h"
|
# include "duffle/lottes_tape.h"
|
||||||
|
# include "duffle/atom_dsl.h"
|
||||||
# include "hello_gte.h"
|
# include "hello_gte.h"
|
||||||
# include "tape_atom.metadata.h"
|
# include "tape_atom.metadata.h"
|
||||||
# include "gen/hello_gte_tape.offsets.h"
|
# include "gen/hello_gte_tape.offsets.h"
|
||||||
@@ -22,7 +23,14 @@
|
|||||||
|
|
||||||
#pragma region Baked Atoms
|
#pragma region Baked Atoms
|
||||||
|
|
||||||
internal MipsAtom_(floor_tri) {
|
atom_region( floor_tri, REGION_PRIM_ARENA)
|
||||||
|
atom_group( floor_tri, GROUP_RENDER_FLOOR)
|
||||||
|
atom_cadence(floor_tri, CADENCE_FRAME)
|
||||||
|
atom_annot( floor_tri, phase_work,
|
||||||
|
tape_regs(R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase),
|
||||||
|
tape_regs(R_PrimCursor, R_FaceCursor))
|
||||||
|
internal
|
||||||
|
MipsAtom_(floor_tri) {
|
||||||
// T0-T2 allocated
|
// T0-T2 allocated
|
||||||
mac_load_tri_indices(R_T0, R_T1, R_T2),
|
mac_load_tri_indices(R_T0, R_T1, R_T2),
|
||||||
// load_half_u(R_T0, R_FaceCur, 0 * S_(S2))
|
// load_half_u(R_T0, R_FaceCur, 0 * S_(S2))
|
||||||
|
|||||||
@@ -41,7 +41,3 @@ WORD_COUNT(mac_insert_ot_tag, 11)
|
|||||||
WORD_COUNT(mac_yield, 4)
|
WORD_COUNT(mac_yield, 4)
|
||||||
|
|
||||||
#undef WORD_COUNT
|
#undef WORD_COUNT
|
||||||
|
|
||||||
// Used to define word markers for the lua metaprogram to calculate offsets from.
|
|
||||||
#define atom_label(sym)
|
|
||||||
// #define atom_offset(sym) // will be generated based on usage within a baked atom.
|
|
||||||
|
|||||||
+88
-27
@@ -81,6 +81,19 @@ $path_psyq = join-path $path_toolchain 'psyq-4_7'
|
|||||||
$path_psyq_iwyu = join-path $path_toolchain 'psyq_iwyu'
|
$path_psyq_iwyu = join-path $path_toolchain 'psyq_iwyu'
|
||||||
$path_psyq_imyu_inc = join-path $path_psyq_iwyu 'include'
|
$path_psyq_imyu_inc = join-path $path_psyq_iwyu 'include'
|
||||||
|
|
||||||
|
function Get-SourceFiles { param([Parameter(Mandatory=$true)] [string[]]$paths, [Parameter(Mandatory=$true)] [string[]]$extensions)
|
||||||
|
$files = @()
|
||||||
|
foreach ($p in $paths) {
|
||||||
|
if (-not (test-path $p)) { continue }
|
||||||
|
foreach ($ext in $extensions) {
|
||||||
|
Get-ChildItem -Path $p -File -Recurse -Filter "*$ext" -ErrorAction SilentlyContinue | ForEach-Object {
|
||||||
|
$files += $_.FullName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ($files | Sort-Object -Unique)
|
||||||
|
}
|
||||||
|
|
||||||
function assemble-unit { param(
|
function assemble-unit { param(
|
||||||
[string] $unit,
|
[string] $unit,
|
||||||
[string] $link_module,
|
[string] $link_module,
|
||||||
@@ -154,11 +167,7 @@ function compile-unit { param(
|
|||||||
& $Compiler $compile_args
|
& $Compiler $compile_args
|
||||||
if ($LASTEXITCODE -ne 0) { write-error "Compilation failed for $unit. Aborting."; exit 1 }
|
if ($LASTEXITCODE -ne 0) { write-error "Compilation failed for $unit. Aborting."; exit 1 }
|
||||||
}
|
}
|
||||||
function link-modules { param(
|
function link-modules { param([string[]]$link_modules, [string] $elf, [string[]]$user_link_args)
|
||||||
[string[]]$link_modules,
|
|
||||||
[string] $elf,
|
|
||||||
[string[]]$user_link_args
|
|
||||||
)
|
|
||||||
$link_args = @()
|
$link_args = @()
|
||||||
|
|
||||||
$link_args += $f_no_stdlib
|
$link_args += $f_no_stdlib
|
||||||
@@ -226,10 +235,7 @@ function link-modules { param(
|
|||||||
& mipsel-none-elf-objdump.exe -W $elf >> $dasm
|
& mipsel-none-elf-objdump.exe -W $elf >> $dasm
|
||||||
if ($LASTEXITCODE -ne 0) { write-error "Linking failed. Aborting."; exit 1 }
|
if ($LASTEXITCODE -ne 0) { write-error "Linking failed. Aborting."; exit 1 }
|
||||||
}
|
}
|
||||||
function make-binary { param(
|
function make-binary { param([string]$elf, [string]$exe)
|
||||||
[string]$elf,
|
|
||||||
[string]$exe
|
|
||||||
)
|
|
||||||
Write-Host "--- Creating Binary ---" -ForegroundColor Cyan
|
Write-Host "--- Creating Binary ---" -ForegroundColor Cyan
|
||||||
write-host "Converting $elf to PS-EXE -> '$exe'"
|
write-host "Converting $elf to PS-EXE -> '$exe'"
|
||||||
$objcopy_args = ($f_objcopy_format + "binary"), $elf, $exe
|
$objcopy_args = ($f_objcopy_format + "binary"), $elf, $exe
|
||||||
@@ -311,12 +317,7 @@ function build-graphis_hello {
|
|||||||
}
|
}
|
||||||
# build-graphis_hello
|
# build-graphis_hello
|
||||||
|
|
||||||
function generate-TapeAtomOffsets {param(
|
function generate-TapeAtomOffsets {param([Parameter(Mandatory=$true)] [string[]]$sources, [Parameter(Mandatory=$true)] [string]$metadata)
|
||||||
[Parameter(Mandatory=$true)]
|
|
||||||
[string[]]$sources,
|
|
||||||
[Parameter(Mandatory=$true)]
|
|
||||||
[string]$metadata)
|
|
||||||
|
|
||||||
$gen_atom_offsets_script = join-path $path_scripts 'tape_atom.offset_gen.meta.lua'
|
$gen_atom_offsets_script = join-path $path_scripts 'tape_atom.offset_gen.meta.lua'
|
||||||
|
|
||||||
$any_stale = $false
|
$any_stale = $false
|
||||||
@@ -337,11 +338,11 @@ function generate-TapeAtomOffsets {param(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (-not $any_stale) {
|
if (-not $any_stale) {
|
||||||
write-host "AtomOffs all $($sources.Count) source(s) up-to-date" -ForegroundColor DarkGray
|
write-host "AtomOffsets all $($sources.Count) source(s) up-to-date" -ForegroundColor DarkGray
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
write-host "AtomOffs $($sources.Count) source(s)" -ForegroundColor Magenta
|
write-host "AtomOffsets $($sources.Count) source(s)" -ForegroundColor Magenta
|
||||||
& lua $gen_atom_offsets_script $metadata @sources
|
& lua $gen_atom_offsets_script $metadata @sources
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
write-error "Atom offset generation failed. Aborting."
|
write-error "Atom offset generation failed. Aborting."
|
||||||
@@ -349,21 +350,81 @@ function generate-TapeAtomOffsets {param(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generate-TapeAtomAnnotations {param([Parameter(Mandatory=$true)] [string[]]$sources, [Parameter(Mandatory=$true)] [string]$metadata)
|
||||||
|
# Sibling to generate-TapeAtomOffsets. Validates TAPE_ATOM_* / TAPE_WORDS
|
||||||
|
# annotations against the metadata manifest. Emits gen/<basename>.errors.h
|
||||||
|
# containing #error directives for the C build to fail on annotation drift.
|
||||||
|
$gen_atom_annot_script = join-path $path_scripts 'tape_atom_annotation_pass.lua'
|
||||||
|
|
||||||
|
$any_stale = $false
|
||||||
|
foreach ($src in $sources) {
|
||||||
|
$basename = [System.IO.Path]::GetFileNameWithoutExtension($src)
|
||||||
|
$dir = split-path -Path $src -Parent
|
||||||
|
$gen_dir = join-path $dir 'gen'
|
||||||
|
$out_txt = join-path $gen_dir "$basename.annotations.txt"
|
||||||
|
$out_err = join-path $gen_dir "$basename.errors.h"
|
||||||
|
|
||||||
|
if (-not (test-path $out_txt) -or -not (test-path $out_err)) { $any_stale = $true; break }
|
||||||
|
$src_mtime = (get-item $src).LastWriteTimeUtc
|
||||||
|
$out_txt_mtime = (get-item $out_txt).LastWriteTimeUtc
|
||||||
|
$out_err_mtime = (get-item $out_err).LastWriteTimeUtc
|
||||||
|
$out_mtime = if ($out_txt_mtime -gt $out_err_mtime) { $out_txt_mtime } else { $out_err_mtime }
|
||||||
|
$meta_mtime = (get-item $metadata).LastWriteTimeUtc
|
||||||
|
if (($src_mtime -gt $out_mtime) -or ($meta_mtime -gt $out_mtime)) {
|
||||||
|
$any_stale = $true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $any_stale) {
|
||||||
|
write-host "AtomAnnotations all $($sources.Count) source(s) up-to-date" -ForegroundColor DarkGray
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
write-host "AtomAnnotations $($sources.Count) source(s)" -ForegroundColor Magenta
|
||||||
|
& lua $gen_atom_annot_script $metadata @sources
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
write-error "Atom annotation generation failed. Aborting."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# If any source produced annotation errors, surface them now and halt the
|
||||||
|
# build. The errors.h files are also #include'd via -include below, so
|
||||||
|
# the C build would fail at preprocessing time anyway — failing here gives
|
||||||
|
# a more readable error in the build log.
|
||||||
|
$err_count = 0
|
||||||
|
foreach ($src in $sources) {
|
||||||
|
$basename = [System.IO.Path]::GetFileNameWithoutExtension($src)
|
||||||
|
$dir = split-path -Path $src -Parent
|
||||||
|
$gen_dir = join-path $dir 'gen'
|
||||||
|
$ann_txt = join-path $gen_dir "$basename.annotations.txt"
|
||||||
|
$err_h = join-path $gen_dir "$basename.errors.h"
|
||||||
|
if ((test-path $ann_txt) -and (test-path $err_h)) {
|
||||||
|
$txt = get-content $ann_txt -raw
|
||||||
|
if ($txt -match 'Errors:\s+([1-9]\d*)') {
|
||||||
|
$err_count += [int]$Matches[1]
|
||||||
|
write-warning "Annotation errors in $src — see $ann_txt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($err_count -gt 0) {
|
||||||
|
write-error "Annotation pass failed: $err_count error(s) across $($sources.Count) source(s). Aborting."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function build-gte_hello {
|
function build-gte_hello {
|
||||||
$includes += @()
|
$includes += @()
|
||||||
|
|
||||||
$path_module = join-path $path_code 'gte_hello'
|
$path_module = join-path $path_code 'gte_hello'
|
||||||
|
$path_duffle = join-path $path_code 'duffle'
|
||||||
|
$path_atom_metadata = join-path $path_module 'tape_atom.metadata.h'
|
||||||
|
|
||||||
$path_duffle = join-path $path_code 'duffle'
|
$source_dirs = @($path_duffle, $path_module)
|
||||||
$path_gen = join-path $path_module 'gen'
|
$atom_sources = Get-SourceFiles -paths $source_dirs -extensions @('.h', '.c')
|
||||||
$path_atom_metadata = join-path $path_module 'tape_atom.metadata.h'
|
|
||||||
|
|
||||||
$atom_sources = @(
|
generate-TapeAtomAnnotations -sources $atom_sources -metadata $path_atom_metadata
|
||||||
(join-path $path_duffle 'mips.h'),
|
generate-TapeAtomOffsets -sources $atom_sources -metadata $path_atom_metadata
|
||||||
(join-path $path_duffle 'lottes_tape.h'),
|
|
||||||
(join-path $path_module 'hello_gte_tape.c')
|
|
||||||
)
|
|
||||||
generate-TapeAtomOffsets -sources $atom_sources -metadata $path_atom_metadata
|
|
||||||
|
|
||||||
$assemble_args = @()
|
$assemble_args = @()
|
||||||
$assemble_args += $f_debug
|
$assemble_args += $f_debug
|
||||||
|
|||||||
@@ -502,9 +502,9 @@ local function generate_header(source_path, atoms_data)
|
|||||||
add("")
|
add("")
|
||||||
add("#pragma region " .. basename)
|
add("#pragma region " .. basename)
|
||||||
add("")
|
add("")
|
||||||
add("// Dispatch macro: token-pastes <tag>_<target> to the enum name")
|
-- add("// Dispatch macro: token-pastes <tag>_<target> to the enum name")
|
||||||
add("#undef atom_offset")
|
-- add("#undef atom_offset")
|
||||||
add("#define atom_offset(tag, name) atom_offset_##tag##_##name")
|
-- add("#define atom_offset(tag, name) atom_offset_##tag##_##name")
|
||||||
add("")
|
add("")
|
||||||
for _, atom in ipairs(atoms_data) do
|
for _, atom in ipairs(atoms_data) do
|
||||||
if #atom.offsets > 0 then
|
if #atom.offsets > 0 then
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user