*.map
*.o
*.obj
+*.rsppch
*.tsk
*.z64
# priv_include is not "advertised", but this is just a demo...
CFLAGS = -Wall -Wextra -pedantic -std=c99 -Wno-main \
- -I../libn64/include -I../libn64 -I../libn64/priv_include -I.
+ -I../libn64/include -I../libgfx/include -I../libn64/priv_include -I.
OPTFLAGS = -Os -march=vr4300 -mtune=vr4300 -mabi=eabi -mgp32 -mlong32 \
-flto -ffat-lto-objects -ffunction-sections -fdata-sections \
@$(OBJCOPY) -O binary $< $@
@$(CHECKSUM) $(call FIXPATH,../libn64/header.bin) $@
-$(ROM_NAME).elf: libn64 $(OBJFILES) filesystem.obj
+$(ROM_NAME).elf: libn64 libgfx $(OBJFILES) filesystem.obj
@echo $(call FIXPATH,"Building: $(ROM_NAME)/$@")
@$(CC) $(CFLAGS) $(OPTFLAGS) -Wl,-Map=$(ROM_NAME).map -nostdlib \
-T$(call FIXPATH,../libn64/rom.ld) -o $@ $(OBJFILES) filesystem.obj \
- -L$(call FIXPATH,../libn64) -ln64
+ -L$(call FIXPATH,../libn64) -ln64 -L$(call FIXPATH,../libgfx) -lgfx
#
# Filesystem build target.
libn64:
@$(MAKE) -sC $(call FIXPATH,../libn64)
+.PHONY: libgfx
+libgfx:
+ @$(MAKE) -sC $(call FIXPATH,../libgfx)
+
#
# Clean project target.
#
--- /dev/null
+#
+# libgfx/Makefile: Makefile for libgfx.
+#
+# n64chain: A (free) open-source N64 development toolchain.
+# Copyright 2014-16 Tyler J. Stachecki <stachecki.tyler@gmail.com>
+#
+# This file is subject to the terms and conditions defined in
+# 'LICENSE', which is part of this source code package.
+#
+
+ifdef SystemRoot
+FIXPATH = $(subst /,\,$1)
+RM = del /Q
+else
+FIXPATH = $1
+RM = rm -f
+endif
+
+AS = $(call FIXPATH,$(CURDIR)/../tools/bin/mips64-elf-as)
+AR = $(call FIXPATH,$(CURDIR)/../tools/bin/mips64-elf-gcc-ar)
+CC = $(call FIXPATH,$(CURDIR)/../tools/bin/mips64-elf-gcc)
+CPP = $(call FIXPATH,$(CURDIR)/../tools/bin/mips64-elf-cpp)
+
+RSPASM = $(call FIXPATH,$(CURDIR)/../tools/bin/rspasm)
+
+CFLAGS = -Wall -Wextra -pedantic -std=c99 -I. -Iinclude -I../libn64/include
+OPTFLAGS = -Os -march=vr4300 -mtune=vr4300 -mabi=eabi -mgp32 -mlong32 \
+ -flto -ffat-lto-objects -ffunction-sections -fdata-sections \
+ -G4 -mno-extern-sdata -mgpopt -mfix4300 -mbranch-likely \
+ -mno-check-zero-division -mno-memcpy
+
+ASMFILES = $(call FIXPATH,\
+)
+
+CFILES = $(call FIXPATH,\
+ src/init.c \
+ src/rspbuf.c \
+)
+
+UCODES = $(call FIXPATH,\
+ ucodes/gfx.rsp \
+)
+
+OBJFILES = \
+ $(CFILES:.c=.o) \
+ $(ASMFILES:.s=.o) \
+ $(UCODES:.rsp=.o)
+
+DEPFILES = $(OBJFILES:.o=.d)
+
+#
+# Primary targets.
+#
+libgfx.a: $(OBJFILES)
+ @echo $(call FIXPATH,"Building: libgfx/$@")
+ @$(AR) rcs $@ $^
+
+#
+# Generic compilation/assembly targets.
+#
+%.o: %.s
+ @echo $(call FIXPATH,"Assembling: libgfx/$<")
+ @$(CC) -x assembler-with-cpp $(CFLAGS) $(OPTFLAGS) -MMD -c $< -o $@
+
+%.o: %.c
+ @echo $(call FIXPATH,"Compiling: libgfx/$<")
+ @$(CC) $(CFLAGS) $(OPTFLAGS) -MMD -c $< -o $@
+
+%.o: %.rsp %.rsps
+ @echo $(call FIXPATH,"Assembling: $(ROM_NAME)/$@")
+ @$(CPP) -E -I../libn64/ucodes -Iucodes $< > $(<:.rsp=.rsppch)
+ @$(RSPASM) $(<:.rsp=.rsppch) -o $(<:.rsp=.bin)
+ @$(CC) -x assembler-with-cpp $(CFLAGS) $(OPTFLAGS) -MMD -c $(<:.rsp=.rsps) -o $@
+
+#
+# Clean project target.
+#
+.PHONY: clean
+clean:
+ @echo "Cleaning libgfx..."
+ @$(RM) libgfx.a $(DEPFILES) $(OBJFILES)
+
--- /dev/null
+//
+// libgfx/include/libgfx/init.h: Graphics initialization.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-16 Tyler J. Stachecki <stachecki.tyler@gmail.com>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#ifndef LIBGFX_INCLUDE_LIBGFX_INIT_H
+#define LIBGFX_INCLUDE_LIBGFX_INIT_H
+
+#include <rcp/sp.h>
+#include <stdint.h>
+
+// Initialize the libgfx components.
+void libgfx_init(void);
+
+// Run the microcode and wait for it to complete.
+static inline void libgfx_run(void) {
+ libn64_rsp_set_pc(0x04001000);
+ libn64_rsp_set_status(RSP_STATUS_CLEAR_HALT | RSP_STATUS_CLEAR_BROKE);
+ while ((libn64_rsp_get_status() & 0x3) != 0x3);
+}
+
+#endif
+
--- /dev/null
+//
+// libgfx/include/libgfx/rspbuf.h: RSP buffer.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-16 Tyler J. Stachecki <stachecki.tyler@gmail.com>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#ifndef LIBGFX_INCLUDE_LIBGFX_RSPBUF_H
+#define LIBGFX_INCLUDE_LIBGFX_RSPBUF_H
+
+#include <libgfx/vertex.h>
+#include <stdint.h>
+#include <syscall.h>
+
+// Both the VR4300 and RSP use the first 16 bytes for "private"
+// storage. The rest of the struct is mapped 1:1 to RSP DMEM.
+struct libgfx_rspbuf {
+ uint16_t head, tail;
+ uint32_t unused[3];
+
+ uint8_t buf[0xC00 - 0x10];
+ struct libgfx_vertex vertices[0x400 / sizeof(struct libgfx_vertex)];
+};
+
+// Appends a word to the RSP buffer structure.
+// Does NOT check for overflow of the buffer.
+static inline void libgfx_rspbuf_append(
+ struct libgfx_rspbuf *rspbuf, uint32_t word) {
+ uint8_t *dest = rspbuf->buf + rspbuf->tail;
+
+ __builtin_memcpy(dest, &word, sizeof(word));
+ rspbuf->tail += sizeof(word);
+}
+
+// Allocates and initializes a new RSP buffer structure.
+static inline struct libgfx_rspbuf *libgfx_rspbuf_create(void) {
+ void *page = libn64_page_alloc();
+ struct libgfx_rspbuf *rspbuf = (struct libgfx_rspbuf *) page;
+
+ rspbuf->head = rspbuf->tail = 0;
+ return rspbuf;
+}
+
+// Frees an existing RSP buffer structure.
+static inline void libgfx_rspbuf_destroy(struct libgfx_rspbuf *rspbuf) {
+ libn64_page_free(rspbuf);
+}
+
+// Flushes the RSP buffer structure to RSP DRAM.
+void libgfx_rspbuf_flush(struct libgfx_rspbuf *rspbuf);
+void libgfx_rspbuf_flush_vertices(struct libgfx_rspbuf *rspbuf);
+
+#endif
+
--- /dev/null
+//
+// libgfx/include/libgfx/vertex.h: Vertex structure.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-16 Tyler J. Stachecki <stachecki.tyler@gmail.com>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#ifndef LIBGFX_INCLUDE_VERTEX_H
+#define LIBGFX_INCLUDE_VERTEX_H
+
+#include <stdint.h>
+
+// This is how vertices are stored within the DRAM cache.
+//
+// While it would be nice to have multiple vertex formats, consider:
+// * The RSP can only do 8-byte aligned transfers
+// * The RSP must quickly calc vertex offset (powers of two...)
+//
+// So a vertex structure /needs/ to be either 8 or 16-bytes...
+struct libgfx_vertex {
+ int16_t x, y; // 10.2 screen coordinates
+ int16_t s, t; // 10.5 texture coordinates
+ int32_t z; // 15.16 z-buffer depth
+
+ union {
+ uint32_t rgba32;
+ uint8_t rgba[4];
+ } color;
+} __attribute__((aligned(16)));
+
+// Write back the contents of the vertex (in cache) to DRAM.
+// If the line has already been flushed, this is a NO-OP.
+static inline void libgfx_vertex_flush(struct libgfx_vertex *v) {
+ __builtin_mips_cache(0x19, v);
+}
+
+// Flushes the cache line that vertex would live in if it is
+// dirty. Unconditionally flags the resulting cache line as
+// 'dirty' and sets the physical tag without loading the
+// current contents of the memory pointed to by 'v'.
+static inline void libgfx_vertex_init(struct libgfx_vertex *v) {
+ __builtin_mips_cache(0xD, v);
+}
+
+#endif
+
--- /dev/null
+//
+// libgfx/src/init.c: Graphics initialization.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-16 Tyler J. Stachecki <stachecki.tyler@gmail.com>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#include <libgfx/init.h>
+#include <rcp/sp.h>
+#include <stdint.h>
+
+void libgfx_init(void) {
+ extern char libn64_ucode_gfx;
+ static const char *ucode_ptr = &libn64_ucode_gfx;
+ uint32_t ucode_address;
+
+ libn64_rsp_set_status(RSP_STATUS_SET_HALT);
+
+ // DMA the initialization ucode to the RSP and spin until its done.
+ __builtin_memcpy(&ucode_address, &ucode_ptr, sizeof(ucode_address));
+ ucode_address &= 0xFFFFFF;
+
+ while (libn64_rsp_is_dma_pending());
+ libn64_rsp_dma_to_rsp(0x04001000, ucode_address + 0x1000, 0xFFF);
+ while (libn64_rsp_is_dma_pending());
+}
+
--- /dev/null
+//
+// libgfx/src/rspbuf.c: RSP buffer.
+//
+// n64chain: A (free) open-source N64 development toolchain.
+// Copyright 2014-16 Tyler J. Stachecki <stachecki.tyler@gmail.com>
+//
+// This file is subject to the terms and conditions defined in
+// 'LICENSE', which is part of this source code package.
+//
+
+#include <libgfx/rspbuf.h>
+#include <libgfx/vertex.h>
+#include <rcp/sp.h>
+#include <stdint.h>
+
+// Flushes the RSP buffer structure to RSP DRAM.
+void libgfx_rspbuf_flush(struct libgfx_rspbuf *rspbuf) {
+ unsigned len = (rspbuf->tail - rspbuf->head + 0xF) & ~0xF;
+
+ if (len > 0) {
+ uint32_t dram_address;
+ unsigned i;
+
+ for (i = 0; i < len; i += 16)
+ __builtin_mips_cache(0x19, rspbuf->buf + i);
+
+ // Spin up an RSP DMA from RDRAM to DMEM.
+ // TODO: In the future, this should be queued up or done from RSP.
+ __builtin_memcpy(&dram_address, &rspbuf, sizeof(dram_address));
+ dram_address = (dram_address & 0xFFFFFF) + rspbuf->head + 0x10;
+
+ libn64_rsp_dma_to_rsp(0x04000010 + rspbuf->head, dram_address, len - 1);
+ while (libn64_rsp_is_dma_pending());
+
+ // Flushed everything; reset the buffer pointer;
+ rspbuf->head = rspbuf->tail = 0;
+ }
+}
+
+// Flushes the RSP vertices buffer structure to RSP DRAM.
+void libgfx_rspbuf_flush_vertices(struct libgfx_rspbuf *rspbuf) {
+ uint32_t dram_address;
+ unsigned i;
+
+ for (i = 0; i < sizeof(rspbuf->vertices) / sizeof(*rspbuf->vertices); i++)
+ libgfx_vertex_flush(rspbuf->vertices + i);
+
+ // Spin up an RSP DMA from RDRAM to DMEM.
+ // TODO: In the future, this should be queued up or done from RSP.
+ __builtin_memcpy(&dram_address, &rspbuf, sizeof(dram_address));
+ dram_address = (dram_address & 0xFFFFFF) + 0xC00;
+
+ libn64_rsp_dma_to_rsp(0x0400C00, dram_address, 0x400 - 1);
+ while (libn64_rsp_is_dma_pending());
+}
+
--- /dev/null
+#include "defs.h"
+
+.set COMMAND_WORD, $1
+.set COMMAND_POINTER, $27
+.set OUTPUT_POINTER, $28
+.set VERTEX_CACHE_OFFSET, 0xC00
+
+.text
+ addiu $30, $0, VERTEX_CACHE_OFFSET
+ addiu COMMAND_POINTER, $0, 0x10
+ addiu OUTPUT_POINTER, $0, 0x100
+
+loop:
+ ; Check if command is RSP processing command.
+ lw COMMAND_WORD, 0x0(COMMAND_POINTER)
+ bltz COMMAND_WORD, draw_triangle
+ addiu COMMAND_POINTER, COMMAND_POINTER, 0x4
+
+ ; Check if command is end of display list.
+ beq COMMAND_WORD, $0, finish
+ nop
+
+ ; Copy RDP command through to output stream.
+ sw COMMAND_WORD, 0x0(OUTPUT_POINTER)
+ lw COMMAND_WORD, 0x0(COMMAND_POINTER)
+ addiu COMMAND_POINTER, COMMAND_POINTER, 0x4
+ sw COMMAND_WORD, 0x4(OUTPUT_POINTER)
+ beq $0, $0, loop
+ addiu OUTPUT_POINTER, OUTPUT_POINTER, 0x8
+
+finish:
+ lui $at, 0x0400
+ ori $at, $at, 0x100
+ mtc0 $at, CMD_START
+
+ lui $at, 0x0400
+ addu $at, $at, OUTPUT_POINTER
+ mtc0 $at, CMD_END
+
+ break
+ nop
+
+; -------------------------------------------------------------------
+; draw_triangle: Given three vertices, pack an RDP Edge Coefficients
+; instruction (for a triangle primitive) accordingly.
+;
+; Steps:
+; * Sort vertices by y-coordinate (ascending).
+; * Compute edge walker slopes and cross-product.
+; * Utilize cross-product to determine if lft or not.
+;
+; TODO:
+; * Handle "huge" (16-bit, heh) reciprocal numbers better?
+; * Accumulate fractional portion of the reciprocal number?
+; -------------------------------------------------------------------
+draw_triangle:
+
+ ; Load offsets of vertices in cache into $2, $3, $4.
+ ; Pre-load the y-coordinates into $5, $6, $7.
+ .set vert1, $2
+ .set vert2, $3
+ .set vert3, $4
+ .set vert1y, $5
+ .set vert2y, $6
+ .set vert3y, $7
+ .set temp, $8
+
+ andi vert3, COMMAND_WORD, 0xFF0
+ srl vert1, COMMAND_WORD, 16
+ andi vert1, vert1, 0xFF0
+ srl vert2, COMMAND_WORD, 8
+ andi vert2, vert2, 0xFF0
+
+ addiu vert3, vert3, VERTEX_CACHE_OFFSET
+ addiu vert2, vert2, VERTEX_CACHE_OFFSET
+ addiu vert1, vert1, VERTEX_CACHE_OFFSET
+
+ ; Sort vertices based on their y-coordinates (ascending).
+ ; Effectively, this is just a space-optimized bubble sort.
+vert_reload_123:
+ lh vert1y, 2(vert1)
+
+vert_reload_23:
+ lh vert2y, 2(vert2)
+ lh vert3y, 2(vert3)
+
+ slt temp, vert2y, vert1y
+ beq temp, $0, vert_comp_23
+ slt temp, vert3y, vert2y
+ xor vert1, vert1, vert2
+ xor vert2, vert2, vert1
+ xor vert1, vert1, vert2
+
+ ; Spend extra two instructions to prevent a worst-case scenario.
+ ; it is possible we have to backwards branch /twice/ if not...
+ slt temp, vert3y, vert1y
+ bne temp, $0, vert_reload_23
+ addiu temp, $0, 0x1
+
+vert_comp_23:
+ beq temp, $0, vertsorted
+ addu temp, vert3, $0
+ addu vert3, vert2, $0
+ beq $0, $0, vert_reload_123
+ addu vert2, temp, $0
+
+ .unset vert1y
+ .unset vert2y
+ .unset vert3y
+ .unset temp
+
+ ; Vertices are y-sorted, vert1 < vert2 < vert3.
+vertsorted:
+ .set deltas, $v0
+ .set verthighmidhigh, $v1
+ .set vertlowlowmid, $v2
+ .set invipart, $v3
+ .set invfpart, $v4
+ .set vrcptemp, $v5
+
+ ; Compute deltas for edge slopes and the cross product.
+ ; Try to fill otherwise dead delay slots with VRCP work.
+ ;
+ ; BTW, if /dy is 0, the RCP calculated is the most +ive
+ ; signed number possible. And lim x->0 1/x = + infinity.
+ ; So we do not need to check/handle division by zero...
+ llv verthighmidhigh, 0x0(vert3)
+ llv vertlowlowmid, 0x0(vert1)
+ vsub vrcptemp, verthighmidhigh, vertlowlowmid
+ llv verthighmidhigh[0x4], 0x0(vert2)
+ llv vertlowlowmid[0x4], 0x0(vert1)
+ vrcp invfpart[1], vrcptemp[1] ; 1 / hdy
+ vrcph invipart[1], $v31[0x8]
+ vsub vrcptemp, verthighmidhigh, vertlowlowmid
+ llv verthighmidhigh[0x8], 0x0(vert3)
+ llv vertlowlowmid[0x8], 0x0(vert2)
+ vrcp invfpart[3], vrcptemp[3] ; 1 / mdy
+ vrcph invipart[3], $v31[0x8]
+ vsub deltas, verthighmidhigh, vertlowlowmid
+ vrcp invfpart[5], deltas[5] ; 1 / ldy
+ vrcph invipart[5], $v31[0x8]
+
+ .unset verthighmidhigh
+ .unset vertlowlowmid
+ .unset vrcptemp
+
+ ; Cross product to determine LFT/RFT (only need sign...)
+ ; delta[0] (dxh) * delta[3] (dym)
+ ; - delta[1] (dyh) * delta[2] (dxm)
+ .set dxhdym, $v5
+ .set dyhdxm, $v6
+ vmudh dxhdym, deltas, deltas[0x7] ; 3h
+ vmudh dyhdxm, deltas, deltas[0x6] ; 2h
+ vsub dxhdym, dxhdym, dyhdxm[0x3] ; 1q
+ mfc2 $1, dxhdym[0x0]
+
+ ; Reciprocals are calculated, and, furthermore:
+ ; delta[0,1] = xh-xl, yh-yl (hdy)
+ ; delta[2,3] = xm-xl, ym-yl (mdy)
+ ; delta[4,5] = xh-xm, yh-ym (ldy)
+
+ ; Bring screen coordinates (subpixel) down to fractional form.
+ .set deltasi, $v1
+ .set deltasf, $v2
+ vmudm deltasi, deltas, $v31[0x9]
+ vmadn deltasf, $v31, $v31[0x8]
+
+ ; OK, we have the reciprocal NUMBER (x where i.e,. 1 / x). To get
+ ; that to screen coordinates, instead of multiplying by 1/4, we
+ ; multiply by 4. Oh, and since VRCP shifts the radix right by one,
+ ; we have to account for that... so 8. Confusing? Yeah...
+ ;
+ ; ... and what the heck happens to really big reciprocal numbers?
+ vmudn invfpart, invfpart, $v31[0xA]
+ vmadh invipart, invipart, $v31[0xA]
+ vmadn invfpart, $v31, $v31[0x8]
+
+ ; Finish the inverse slope calculations...
+ .set edgeslopei, $v6
+ .set edgeslopef, $v7
+ vmudl edgeslopef, deltasf, invipart[0x3] ; 1q
+ vmadm edgeslopei, deltasi, invipart[0x3] ; 1q
+ vmadn edgeslopef, $v31, $v31[0x8]
+
+ ; Pack the RDP Edge Coefficients instruction.
+ lui $5, 0x0800
+ slt $6, $1, $0
+ sll $6, $6, 23
+ or $5, $5, $6
+ lh $6, 2(vert3)
+ or $5, $5, $6
+ sw $5, 0x0(OUTPUT_POINTER)
+
+ lh $5, 2(vert2)
+ lh $6, 2(vert1)
+ sll $5, $5, 0x10
+ or $5, $5, $6
+ sw $5, 0x4(OUTPUT_POINTER)
+
+ lh $5, 0x0(vert2)
+ sll $5, $5, 0xE
+ sw $5, 0x8(OUTPUT_POINTER)
+
+ lh $5, 0x0(vert1)
+ sll $5, $5, 0xE
+ sw $5, 0x10(OUTPUT_POINTER)
+ sw $5, 0x18(OUTPUT_POINTER)
+
+ ssv edgeslopei[0x8], 0xC(OUTPUT_POINTER)
+ ssv edgeslopef[0x8], 0xE(OUTPUT_POINTER)
+ ssv edgeslopei[0x4], 0x1C(OUTPUT_POINTER)
+ ssv edgeslopef[0x4], 0x1E(OUTPUT_POINTER)
+ ssv edgeslopei[0x0], 0x14(OUTPUT_POINTER)
+ ssv edgeslopef[0x0], 0x16(OUTPUT_POINTER)
+ beq $0, $0, loop
+ addiu OUTPUT_POINTER, OUTPUT_POINTER, 0x20
+
+ ; TODO: Unset everything else...
+ ; Will likely need these still?
+ .unset vert1
+ .unset vert2
+ .unset vert3
+
+.unset COMMAND_WORD
+.unset COMMAND_POINTER
+.unset VERTEX_CACHE_OFFSET
+
--- /dev/null
+#
+# libgfx/ucodes/gfx.rsps: RSP graphics ucode container.
+#
+# n64chain: A (free) open-source N64 development toolchain.
+# Copyright 2014-16 Tyler J. Stachecki <stachecki.tyler@gmail.com>
+#
+# This file is subject to the terms and conditions defined in
+# 'LICENSE', which is part of this source code package.
+#
+
+#include <libn64.h>
+
+.section .text.libn64.ucode, "ax", @progbits
+
+.global libn64_ucode_gfx
+.type libn64_ucode_gfx, @object
+.align 4
+libn64_ucode_gfx:
+.incbin "ucodes/gfx.bin"
+
+.size libn64_ucode_gfx,.-libn64_ucode_gfx
+
%.o: %.rsp %.rsps
@echo $(call FIXPATH,"Assembling: $(ROM_NAME)/$@")
- @$(CPP) -E -Iucodes $< | $(RSPASM) -o $(<:.rsp=.bin) -
+ @$(CPP) -E -Iucodes $< > $(<:.rsp=.rsppch)
+ @$(RSPASM) $(<:.rsp=.rsppch) -o $(<:.rsp=.bin)
@$(CC) -x assembler-with-cpp $(CFLAGS) $(OPTFLAGS) -MMD -c $(<:.rsp=.rsps) -o $@
#
RSPASM = $(call FIXPATH,$(CURDIR)/../tools/bin/rspasm)
CFLAGS = -Wall -Wextra -pedantic -std=c99 -Wno-main \
- -I../libn64/include -I../libn64 -I.
+ -I../libn64/include -I../libgfx/include -I.
OPTFLAGS = -Os -march=vr4300 -mtune=vr4300 -mabi=eabi -mgp32 -mlong32 \
-flto -ffat-lto-objects -ffunction-sections -fdata-sections \
@$(OBJCOPY) -O binary $< $@
@$(CHECKSUM) $(call FIXPATH,../libn64/header.bin) $@
-$(ROM_NAME).elf: libn64 $(OBJFILES) filesystem.obj
+$(ROM_NAME).elf: libn64 libgfx $(OBJFILES) filesystem.obj
@echo $(call FIXPATH,"Building: $(ROM_NAME)/$@")
@$(CC) $(CFLAGS) $(OPTFLAGS) -Wl,-Map=$(ROM_NAME).map -nostdlib \
-T$(call FIXPATH,../libn64/rom.ld) -o $@ $(OBJFILES) filesystem.obj \
- -L$(call FIXPATH,../libn64) -ln64
+ -L$(call FIXPATH,../libn64) -ln64 -L$(call FIXPATH,../libgfx) -lgfx
#
# Filesystem build target.
libn64:
@$(MAKE) -sC $(call FIXPATH,../libn64)
+.PHONY: libgfx
+libgfx:
+ @$(MAKE) -sC $(call FIXPATH,../libgfx)
+
#
# Clean project target.
#