Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add C template #1981

Merged
merged 7 commits into from
Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
49 changes: 49 additions & 0 deletions templates/c/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# C Starter Project Template

## Pre-requisites

- [WASI SDK](https://github.com/WebAssembly/wasi-sdk)

## Files in this template

- ```buildcart.sh``` - convenience script to build and run the game cartridge
- ```buildwasm.sh``` - convenience script to build and run the Wasm program
- ```Makefile``` - convenience Makefile that builds the project
- ```wasmdemo.wasmp``` - TIC-80 Wasm 'script' file. Note the embedded game assets data at the end of the file.

## Building your game

Define the environment variable WASI_SDK_PATH; e.g., if you installed WASI
into ```$HOME/wasi-sdk```, then ```export WASI_SDK_PATH=$HOME/wasi-sdk```.

Edit ```src/main.c``` to implement your game. You are of course free to
organize your code in more than one C source file.

If you create sprites, map, music, etc., for your game, remember to
replace the game asset data at the end of ```wasmdemo.wasmp``` with
your creations.

To build the Wasm file, execute ```make```. This generates ```cart.wasm```
in the build directory. To run:

```
% tic80 --fs . --cmd 'load wasmdemo.wasmp & import binary cart.wasm & run & exit'
```

The script ```buildwasm.sh``` contains above steps as a convenience.

To build a TIC-80 cartridge, first build the Wasm file, then build the
cartridge file:

```
% tic80 --fs . --cmd 'load wasmdemo.wasmp & import binary cart.wasm & save game.tic & exit'
```

You can then run your cartridge as follows:

```
% tic80 --fs . --cmd 'load game.tic & run & exit'
```

The script ```buildcart.sh``` does the above steps as a convenience.

6 changes: 6 additions & 0 deletions templates/c/buildcart.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh
rm -f game.tic
make clean
make
tic80 --fs . --cmd 'load wasmdemo.wasmp & import binary build/cart.wasm & save game.tic & exit'
tic80 --fs . --cmd 'load game.tic & run & exit'
4 changes: 4 additions & 0 deletions templates/c/buildwasm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
make clean
make
tic80 --fs . --cmd 'load wasmdemo.wasmp & import binary build/cart.wasm & run & exit'
50 changes: 50 additions & 0 deletions templates/c/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#include "tic80.h"

#define max(a, b) (a > b) ? a : b
#define min(a, b) (a < b) ? a : b

// From WASI libc:
WASM_IMPORT("snprintf")
int snprintf(char* s, size_t n, const char* format, ...);

int t, x, y;
const char* m = "HELLO WORLD FROM C!";
int r = 0;
MouseData md;
uint8_t transcolors = { 14 };

WASM_EXPORT("BOOT")
void BOOT() {
t = 1;
x = 96;
y = 24;
}

WASM_EXPORT("TIC")
void TIC() {
cls(13);

// The standard demo.
if (btn(0) > 0) { y--; }
if (btn(1) > 0) { y++; }
if (btn(2) > 0) { x--; }
if (btn(3) > 0) { x++; }

spr(1+t%60/30*2, x, y, &transcolors, 1, 3, 0, 0, 2, 2);
print(m, 60, 84, 15, 1, 1, 0);
t++;

// Mouse example demonstrating use of libc function.
mouse(&md);
if (md.left) { r = r + 2; }
r--;
r = max(0, min(32, r));
line(md.x, 0, md.x, 136, 11);
line(0, md.y, 240, md.y, 11);
circ(md.x, md.y, r, 11);

const int BUFSIZ = 10;
char buf[BUFSIZ];
snprintf(buf, BUFSIZ, "(%03d,%03d)", md.x, md.y);
print(buf, 3, 3, 15, 0, 1, 1);
}
208 changes: 208 additions & 0 deletions templates/c/src/tic80.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
#pragma once

#include <stdbool.h>
#include <stdint.h>

#define WASM_EXPORT(name) __attribute__((export_name(name)))
#define WASM_IMPORT(name) __attribute__((import_name(name)))

typedef struct {
uint8_t SCREEN[16320];
uint8_t PALETTE[48];
uint8_t PALETTE_MAP[8];
uint8_t BORDER_COLOR;
uint8_t SCREEN_OFFSET_X;
uint8_t SCREEN_OFFSET_Y;
uint8_t MOUSE_CURSOR;
uint8_t BLIT_SEGMENT;
uint8_t RESERVED[3];
} VRAM;

typedef struct {
short x; short y;
char scrollx; char scrolly;
bool left; bool middle; bool right;
} MouseData;

const int32_t WIDTH = 240;
const int32_t HEIGHT = 136;

// Constants.
const uint32_t TILES_SIZE = 8192;
const uint32_t SPRITES_SIZE = 8192;
const uint32_t MAP_SIZE = 32640;
const uint32_t GAMEPADS_SIZE = 4;
const uint32_t MOUSE_SIZE = 4;
const uint32_t KEYBOARD_SIZE = 4;
const uint32_t SFX_STATE_SIZE = 16;
const uint32_t SOUND_REGISTERS_SIZE = 72;
const uint32_t WAVEFORMS_SIZE = 256;
const uint32_t SFX_SIZE = 4224;
const uint32_t MUSIC_PATTERNS_SIZE = 11520;
const uint32_t MUSIC_TRACKS_SIZE = 408;
const uint32_t SOUND_STATE_SIZE = 4;
const uint32_t STEREO_VOLUME_SIZE = 4;
const uint32_t PERSISTENT_MEMORY_SIZE = 1024;
const uint32_t SPRITE_FLAGS_SIZE = 512;
const uint32_t SYSTEM_FONT_SIZE = 2048;
const uint32_t WASM_FREE_RAM_SIZE = 163840; // 160kb

// Pointers.
VRAM* FRAMEBUFFER = (VRAM*)0;
uint8_t* TILES = (uint8_t*)0x04000;
uint8_t* SPRITES = (uint8_t*)0x06000;
uint8_t* MAP = (uint8_t*)0x08000;
uint8_t* GAMEPADS = (uint8_t*)0x0FF80;
uint8_t* MOUSE = (uint8_t*)0x0FF84;
uint8_t* KEYBOARD = (uint8_t*)0x0FF88;
uint8_t* SFX_STATE = (uint8_t*)0x0FF8C;
uint8_t* SOUND_REGISTERS = (uint8_t*)0x0FF9C;
uint8_t* WAVEFORMS = (uint8_t*)0x0FFE4;
uint8_t* SFX = (uint8_t*)0x100E4;
uint8_t* MUSIC_PATTERNS = (uint8_t*)0x11164;
uint8_t* MUSIC_TRACKS = (uint8_t*)0x13E64;
uint8_t* SOUND_STATE = (uint8_t*)0x13FFC;
uint8_t* STEREO_VOLUME = (uint8_t*)0x14000;
uint8_t* PERSISTENT_MEMORY = (uint8_t*)0x14004;
uint8_t* SPRITE_FLAGS = (uint8_t*)0x14404;
uint8_t* SYSTEM_FONT = (uint8_t*)0x14604;
uint8_t* WASM_FREE_RAM = (uint8_t*)0x18000; // 160kb

// Functions.
WASM_IMPORT("btn")
int32_t btn(int32_t id);

WASM_IMPORT("btnp")
bool btnp(int32_t id, int32_t hold, int32_t period);

WASM_IMPORT("circ")
void circ(int32_t x, int32_t y, int32_t radius, int32_t color);

WASM_IMPORT("circb")
void circb(int32_t x, int32_t y, int32_t radius, int32_t color);

WASM_IMPORT("clip")
void clip(int32_t x, int32_t y, int32_t w, int32_t h);

WASM_IMPORT("cls")
void cls(int32_t color);

WASM_IMPORT("exit")
void exit();

WASM_IMPORT("elli")
void elli(int32_t x, int32_t y, int32_t a, int32_t b, int32_t color);

WASM_IMPORT("ellib")
void ellib(int32_t x, int32_t y, int32_t a, int32_t b, int32_t color);

WASM_IMPORT("fget")
bool fget(int32_t id, uint8_t flag);

WASM_IMPORT("font")
int32_t font(const char* text, int32_t x, int32_t y, uint32_t* transcolors, int32_t colorcount, int32_t width, int32_t height, bool fixed, int32_t scale, bool alt);

WASM_IMPORT("fset")
bool fset(int32_t id, uint8_t flag, bool value);

WASM_IMPORT("key")
bool key(int32_t keycode);

WASM_IMPORT("keyp")
bool keyp(int32_t keycode, int32_t hold, int32_t period);

WASM_IMPORT("line")
void line(float x0, float y0, float x1, float y1, int8_t color);

WASM_IMPORT("map")
void map(int32_t x, int32_t y, int32_t w, int32_t h, int32_t sx, int32_t sy, uint32_t* transcolors, int32_t colorcount, int32_t scale, int32_t remap);

WASM_IMPORT("memcpy")
void memcpy_tic(uint32_t copyto, uint32_t copyfrom, uint32_t length);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would tic_memcpy make more sense? I'm not sure, but this is one of the few pieces that caught my eye. I assume this is because of the C built-in memcpy function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it is due to conflicts with the C standard library. While tic_memcpy, would be the more normal way to say the function name, in practice when you want to use memcpy you would probably just start typing it without realizing tic_ should be in front, whereas it being treated as a suffix allows for IntelliSense to catch it.

Having it be a suffix too makes it feel less inconsistent. If two of the function names began with tic_, then it would be strange for not all of them to begin with this prefix.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You make a good case...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As pointed out elsewhere C's own native memcpy would likely be sufficient - there isn't supposed to be any magic.... is tapping into that a possibility?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know that was allowed for this, I assumed TIC's memcpy and memset were special.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They shouldn't be. The WASM VM has access to all the RAM - it should be able to move it about at at will . Really the memcpy and memset make a whole lot less sense when you're in C as you have directly access to RAM. Remember most people are using scripting runtime so the only way to get to the devices 96kb of MMIO is via these special functions, where-as in the WASM runtime all the memory is just freely available.

Copy link
Collaborator

@joshgoebel joshgoebel Jul 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless the API is different though, that's a sticky point... we'd prefer the same TIC API arguments, but if we used native memcpy behind the scenes I think that'd be fine. So is that doable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I have removed these TIC functions and tested the standard memset and it works fine.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets just add a mention to the README that for simplicity and to avoid conflicts one should use the standard C memory functions rather than the TIC api ones...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the README. I think the wording is ok, but I'm not sure.


WASM_IMPORT("memset")
void memset_tic(uint32_t addr, uint8_t value, uint32_t length);

WASM_IMPORT("mget")
int32_t mget(int32_t x, int32_t y);

WASM_IMPORT("mouse")
void mouse(MouseData* data);

WASM_IMPORT("mset")
void mset(int32_t x, int32_t y, bool value);

WASM_IMPORT("music")
void music(int32_t track, int32_t frame, int32_t row, bool loop, bool sustain, int32_t tempo, int32_t speed);

WASM_IMPORT("peek")
uint8_t peek(int32_t addr, int32_t bits);

WASM_IMPORT("peek4")
uint8_t peek4(uint32_t addr4);

WASM_IMPORT("peek2")
uint8_t peek2(uint32_t addr2);

WASM_IMPORT("peek1")
uint8_t peek1(uint32_t bitaddr);

WASM_IMPORT("pix")
void pix(int32_t x, int32_t y, int32_t color);

WASM_IMPORT("pmem")
uint32_t pmem(uint32_t index, uint32_t value);

WASM_IMPORT("poke")
void poke(int32_t addr, int8_t value, int8_t bits);

WASM_IMPORT("poke4")
void poke4(int32_t addr4, int8_t value);

WASM_IMPORT("poke2")
void poke2(int32_t addr2, int8_t value);

WASM_IMPORT("poke1")
void poke1(int32_t bitaddr, int8_t value);

WASM_IMPORT("print")
int32_t print(const char* txt, int32_t x, int32_t y, int32_t color, int32_t fixed, int32_t scale, int32_t alt);

WASM_IMPORT("rect")
void rect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t color);

WASM_IMPORT("rectb")
void rectb(int32_t x, int32_t y, int32_t w, int32_t h, int32_t color);

WASM_IMPORT("reset")
void reset();

WASM_IMPORT("sfx")
void sfx(int32_t id, int32_t note, int32_t octave, int32_t duration, int32_t channel, int32_t volumeLeft, int32_t volumeRight, int32_t speed);

WASM_IMPORT("spr")
void spr(int32_t id, int32_t x, int32_t y, uint8_t* transcolors, uint32_t colorcount, int32_t scale, int32_t flip, int32_t rotate, int32_t w, int32_t h);

WASM_IMPORT("sync")
void sync(int32_t mask, int32_t bank, bool tocart);

WASM_IMPORT("trace")
void trace(const char* txt, int32_t color);

WASM_IMPORT("ttri")
void ttri(float x1, float y1, float x2, float y2, float x3, float y3, float u1, float v1, float u2, float v2, float u3, float v3, int32_t texsrc, uint32_t* transcolors, int32_t colorcount, float z1, float z2, float z3, bool persp);

WASM_IMPORT("tri")
void tri(float x1, float y1, float x2, float y2, float x3, float y3, int32_t color);

WASM_IMPORT("trib")
void trib(float x1, float y1, float x2, float y2, float x3, float y3, int32_t color);

WASM_IMPORT("time")
float time();

WASM_IMPORT("tstamp")
int32_t tstamp();

WASM_IMPORT("vbank")
int32_t vbank(int32_t bank);
57 changes: 57 additions & 0 deletions templates/c/wasmdemo.wasmp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
-- desc: WASM Introduction
-- script: wasm

"""""""""""""""""""""""""""""""""""""""
WASM is a binary format. The demo
binary code is embedded in this
cartridge, the source code is not.
Run the cart to see the demo.

This demo exits for completeness, but
you can't (yet) develop WASM projects
using the built-in editor.

The code used to build this project
can be found in the TIC-80 repo:

https://github.com/nesbox/TIC-80/templates/c

This demo was built with C, but many
languages are supported. You simply
build your project with your external
compiler, then import the final WASM
binary into your cartridge:

import binary out.wasm

See README.md for details.

To learn more visit our Wiki and
the 'Getting Started with WASM' page.

"
-- <TILES>
-- 001:eccccccccc888888caaaaaaaca888888cacccccccacc0ccccacc0ccccacc0ccc
-- 002:ccccceee8888cceeaaaa0cee888a0ceeccca0ccc0cca0c0c0cca0c0c0cca0c0c
-- 003:eccccccccc888888caaaaaaaca888888cacccccccacccccccacc0ccccacc0ccc
-- 004:ccccceee8888cceeaaaa0cee888a0ceeccca0cccccca0c0c0cca0c0c0cca0c0c
-- 017:cacccccccaaaaaaacaaacaaacaaaaccccaaaaaaac8888888cc000cccecccccec
-- 018:ccca00ccaaaa0ccecaaa0ceeaaaa0ceeaaaa0cee8888ccee000cceeecccceeee
-- 019:cacccccccaaaaaaacaaacaaacaaaaccccaaaaaaac8888888cc000cccecccccec
-- 020:ccca00ccaaaa0ccecaaa0ceeaaaa0ceeaaaa0cee8888ccee000cceeecccceeee
-- </TILES>

-- <WAVES>
-- 000:00000000ffffffff00000000ffffffff
-- 001:0123456789abcdeffedcba9876543210
-- 002:0123456789abcdef0123456789abcdef
-- </WAVES>

-- <SFX>
-- 000:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000304000000000
-- </SFX>

-- <PALETTE>
-- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
-- </PALETTE>