summary refs log tree commit diff
path: root/src/Graphics_GCWii.c
diff options
context:
space:
mode:
authorWlodekM <[email protected]>2024-06-16 10:35:45 +0300
committerWlodekM <[email protected]>2024-06-16 10:35:45 +0300
commitabef6da56913f1c55528103e60a50451a39628b1 (patch)
treeb3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Graphics_GCWii.c
initial commit
Diffstat (limited to 'src/Graphics_GCWii.c')
-rw-r--r--src/Graphics_GCWii.c598
1 files changed, 598 insertions, 0 deletions
diff --git a/src/Graphics_GCWii.c b/src/Graphics_GCWii.c
new file mode 100644
index 0000000..d894271
--- /dev/null
+++ b/src/Graphics_GCWii.c
@@ -0,0 +1,598 @@
+#include "Core.h"
+#if defined CC_BUILD_GCWII
+#include "_GraphicsBase.h"
+#include "Errors.h"
+#include "Logger.h"
+#include "Window.h"
+#include <malloc.h>
+#include <string.h>
+#include <gccore.h>
+
+static void* fifo_buffer;
+#define FIFO_SIZE (256 * 1024)
+extern void* Window_XFB;
+static void* xfbs[2];
+static  int curFB;
+static GfxResourceID white_square;
+// https://wiibrew.org/wiki/Developer_tips
+// https://devkitpro.org/wiki/libogc/GX
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------General---------------------------------------------------------*
+*#########################################################################################################################*/
+static void InitGX(void) {
+	GXRModeObj* mode = VIDEO_GetPreferredMode(NULL);
+	fifo_buffer = MEM_K0_TO_K1(memalign(32, FIFO_SIZE));
+	memset(fifo_buffer, 0, FIFO_SIZE);
+
+	GX_Init(fifo_buffer, FIFO_SIZE);
+	Gfx_SetViewport(0, 0, mode->fbWidth, mode->efbHeight);
+	GX_SetDispCopyYScale((f32)mode->xfbHeight / (f32)mode->efbHeight);
+	GX_SetDispCopySrc(0, 0, mode->fbWidth, mode->efbHeight);
+	GX_SetDispCopyDst(mode->fbWidth, mode->xfbHeight);
+	GX_SetCopyFilter(mode->aa, mode->sample_pattern,
+					 GX_TRUE, mode->vfilter);
+	GX_SetFieldMode(mode->field_rendering,
+					((mode->viHeight==2*mode->xfbHeight)?GX_ENABLE:GX_DISABLE));
+
+	GX_SetCullMode(GX_CULL_NONE);
+	GX_SetDispCopyGamma(GX_GM_1_0);
+	GX_InvVtxCache();
+	
+	GX_SetNumChans(1);
+	
+	xfbs[0] = Window_XFB;
+	xfbs[1] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(mode));
+}
+
+void Gfx_Create(void) {
+	if (!Gfx.Created) InitGX();
+	
+	Gfx.MaxTexWidth  = 1024;
+	Gfx.MaxTexHeight = 1024;
+	Gfx.MaxTexSize   = 512 * 512;
+	
+	Gfx.MinTexWidth  = 4;
+	Gfx.MinTexHeight = 4;
+	Gfx.Created      = true;
+	gfx_vsync        = true;
+	
+	Gfx_RestoreState();
+}
+
+void Gfx_Free(void) { 
+	Gfx_FreeState();
+	GX_AbortFrame();
+	//GX_Flush(); // TODO needed?
+	VIDEO_Flush();
+}
+cc_bool Gfx_TryRestoreContext(void) { return true; }
+
+void Gfx_RestoreState(void) { 
+	InitDefaultResources();
+
+	// 4x4 dummy white texture (textures must be at least 1 4x4 tile)
+	struct Bitmap bmp;
+	BitmapCol pixels[4 * 4];
+	Mem_Set(pixels, 0xFF, sizeof(pixels));
+	Bitmap_Init(bmp, 4, 4, pixels);
+	white_square = Gfx_CreateTexture(&bmp, 0, false);
+}
+
+void Gfx_FreeState(void) { 
+	FreeDefaultResources();
+	Gfx_DeleteTexture(&white_square);
+}
+
+/*########################################################################################################################*
+*---------------------------------------------------------Textures--------------------------------------------------------*
+*#########################################################################################################################*/
+typedef struct CCTexture_ {
+	GXTexObj obj;
+	cc_uint32 pixels[];
+} CCTexture;
+
+// ClassiCube RGBA8 bitmaps
+// - store pixels in simple linear order
+//    i.e. pixels are ordered as (x=0,y=0), (x=1,y=0), (x=2,y=0) ... (x=0,y=1) ...
+// - store colour components in interleaved order
+//    i.e. pixels are stored in memory as ARGB ARGB ARGB ARGB ...
+// GX RGBA8 textures
+// - store pixels in 4x4 tiles
+// - store all of the AR values of the tile's pixels, then store all of the GB values
+static void ReorderPixels(CCTexture* tex, struct Bitmap* bmp, 
+			int originX, int originY, int rowWidth) {
+	int stride = GX_GetTexObjWidth(&tex->obj) * 4;
+	// TODO not really right
+	// TODO originX ignored
+	originX &= ~0x03;
+	originY &= ~0x03;
+	
+	// http://hitmen.c02.at/files/yagcd/yagcd/chap15.html
+	//  section 15.35  TPL (Texture Palette)
+	// "RGBA8 (4x4 tiles in two cache lines - first is AR and second is GB"
+	uint8_t *src = (uint8_t*)bmp->scan0;
+	uint8_t *dst = (uint8_t*)tex->pixels + stride * originY;
+	int srcWidth = bmp->width, srcHeight = bmp->height;
+	
+	for (int tileY = 0; tileY < srcHeight; tileY += 4)
+		for (int tileX = 0; tileX < srcWidth; tileX += 4) 
+	{
+		for (int y = 0; y < 4; y++) {
+			for (int x = 0; x < 4; x++) {
+				uint32_t idx = (((tileY + y) * rowWidth) + tileX + x) << 2;
+
+				*dst++ = src[idx + 0]; // A
+				*dst++ = src[idx + 1]; // R
+			}
+		}
+		
+		for (int y = 0; y < 4; y++) {
+			for (int x = 0; x < 4; x++) {
+				uint32_t idx = (((tileY + y) * rowWidth) + tileX + x) << 2;
+
+				*dst++ = src[idx + 2]; // G
+				*dst++ = src[idx + 3]; // B
+			}
+		}
+	}
+}
+
+static GfxResourceID Gfx_AllocTexture(struct Bitmap* bmp, int rowWidth, cc_uint8 flags, cc_bool mipmaps) {
+	int size = bmp->width * bmp->height * 4;
+	CCTexture* tex = (CCTexture*)memalign(32, 32 + size);
+	if (!tex) return NULL;
+	
+	GX_InitTexObj(&tex->obj, tex->pixels, bmp->width, bmp->height,
+			GX_TF_RGBA8, GX_REPEAT, GX_REPEAT, GX_FALSE);
+	GX_InitTexObjFilterMode(&tex->obj, GX_NEAR, GX_NEAR);
+			
+	ReorderPixels(tex, bmp, 0, 0, rowWidth);
+	DCFlushRange(tex->pixels, size);
+	return tex;
+}
+
+void Gfx_UpdateTexture(GfxResourceID texId, int x, int y, struct Bitmap* part, int rowWidth, cc_bool mipmaps) {
+	CCTexture* tex = (CCTexture*)texId;
+	// TODO: wrong behaviour if x/y/part isn't multiple of 4 pixels
+	ReorderPixels(tex, part, x, y, rowWidth);
+	GX_InvalidateTexAll();
+}
+
+void Gfx_DeleteTexture(GfxResourceID* texId) {
+	GfxResourceID data = *texId;
+	if (data) Mem_Free(data);
+	*texId = NULL;
+}
+
+void Gfx_EnableMipmaps(void) { }
+void Gfx_DisableMipmaps(void) { }
+
+void Gfx_BindTexture(GfxResourceID texId) {
+	CCTexture* tex = (CCTexture*)texId;
+	if (!tex) tex = white_square;
+
+	GX_LoadTexObj(&tex->obj, GX_TEXMAP0);
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------State management----------------------------------------------------*
+*#########################################################################################################################*/
+static GXColor gfx_clearColor = { 0, 0, 0, 255 };
+
+void Gfx_SetFaceCulling(cc_bool enabled) { 
+	// NOTE: seems like ClassiCube's triangle ordering is opposite of what GX considers front facing
+	GX_SetCullMode(enabled ? GX_CULL_FRONT : GX_CULL_NONE);
+}
+
+static void SetAlphaBlend(cc_bool enabled) {
+	if (enabled) {
+		GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
+	} else {
+		GX_SetBlendMode(GX_BM_NONE,  GX_BL_ONE, GX_BL_ZERO, GX_LO_CLEAR);
+	}
+}
+
+void Gfx_SetAlphaArgBlend(cc_bool enabled) { 
+}
+
+void Gfx_ClearColor(PackedCol color) {
+	gfx_clearColor.r = PackedCol_R(color);
+	gfx_clearColor.g = PackedCol_G(color);
+	gfx_clearColor.b = PackedCol_B(color);
+	
+	GX_SetCopyClear(gfx_clearColor, 0x00ffffff); // TODO: use GX_MAX_Z24 
+}
+
+static void SetColorWrite(cc_bool r, cc_bool g, cc_bool b, cc_bool a) {
+	// TODO
+}
+
+static cc_bool depth_write = true, depth_test = true;
+static void UpdateDepthState(void) {
+	GX_SetZMode(depth_test, GX_LEQUAL, depth_write);
+}
+
+void Gfx_SetDepthWrite(cc_bool enabled) {
+	depth_write = enabled;
+	UpdateDepthState();
+}
+
+void Gfx_SetDepthTest(cc_bool enabled) {
+	depth_test = enabled;
+	UpdateDepthState();
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------------Misc----------------------------------------------------------*
+*#########################################################################################################################*/
+static BitmapCol* GCWii_GetRow(struct Bitmap* bmp, int y, void* ctx) {
+	u8* buffer = (u8*)ctx;
+	u8 a, r, g, b;
+	int blockYStride = 4 * (bmp->width * 4); // tile row stride = 4 * row stride
+	int blockXStride = (4 * 4) * 4; // 16 pixels per tile
+
+	// Do the inverse of converting from 4x4 tiled to linear
+	for (u32 x = 0; x < bmp->width; x++){
+		int tileY = y >> 2, tileX = x >> 2;
+		int locY  = y & 0x3, locX = x & 0x3;
+		int idx   = (tileY * blockYStride) + (tileX * blockXStride) + ((locY << 2) + locX) * 2; 
+
+		// All 16 pixels are stored with AR first, then GB
+		//a = buffer[idx     ];
+		r = buffer[idx +  1];
+		g = buffer[idx + 32]; 
+		b = buffer[idx + 33];
+
+		bmp->scan0[x] = BitmapColor_RGB(r, g, b);
+	}
+	return bmp->scan0;
+}
+
+cc_result Gfx_TakeScreenshot(struct Stream* output) {
+	BitmapCol tmp[1024];
+	GXRModeObj* vmode = VIDEO_GetPreferredMode(NULL);
+	int width  = vmode->fbWidth;
+	int height = vmode->efbHeight;
+
+	u8* buffer = memalign(32, width * height * 4);
+	if (!buffer) return ERR_OUT_OF_MEMORY;
+
+	GX_SetTexCopySrc(0, 0, width, height);
+	GX_SetTexCopyDst(width, height, GX_TF_RGBA8, GX_FALSE);
+	GX_CopyTex(buffer, GX_FALSE);
+	GX_PixModeSync();
+	GX_Flush();
+	DCFlushRange(buffer, width * height * 4);
+
+	struct Bitmap bmp;
+	bmp.scan0  = tmp;
+	bmp.width  = width; 
+	bmp.height = height;
+
+	cc_result res = Png_Encode(&bmp, output, GCWii_GetRow, false, buffer);
+	free(buffer);
+	return res;
+}
+
+void Gfx_GetApiInfo(cc_string* info) {
+	String_AppendConst(info, "-- Using GC/Wii --\n");
+	PrintMaxTextureInfo(info);
+}
+
+void Gfx_SetFpsLimit(cc_bool vsync, float minFrameMs) {
+	gfx_minFrameMs = minFrameMs;
+	gfx_vsync      = vsync;
+}
+
+void Gfx_BeginFrame(void) {
+}
+
+void Gfx_ClearBuffers(GfxBuffers buffers) {
+	// TODO clear only some buffers
+}
+
+void Gfx_EndFrame(void) {
+	curFB ^= 1; // swap "front" and "back" buffers
+	GX_CopyDisp(xfbs[curFB], GX_TRUE);
+	GX_DrawDone();
+	
+	VIDEO_SetNextFramebuffer(xfbs[curFB]);
+	VIDEO_Flush();
+	
+	if (gfx_vsync) VIDEO_WaitVSync();
+	if (gfx_minFrameMs) LimitFPS();
+}
+
+void Gfx_OnWindowResize(void) { }
+
+void Gfx_SetViewport(int x, int y, int w, int h) {
+	GX_SetViewport(x, y, w, h, 0, 1);
+	GX_SetScissor(x, y, w, h);
+}
+
+cc_bool Gfx_WarnIfNecessary(void) { return false; }
+
+
+/*########################################################################################################################*
+*-------------------------------------------------------Index Buffers-----------------------------------------------------*
+*#########################################################################################################################*/
+//static cc_uint16 __attribute__((aligned(16))) gfx_indices[GFX_MAX_INDICES];
+
+GfxResourceID Gfx_CreateIb2(int count, Gfx_FillIBFunc fillFunc, void* obj) {
+	//fillFunc(gfx_indices, count, obj);
+	// not used since render using GX_QUADS anyways
+	return (void*)1;
+}
+
+void Gfx_BindIb(GfxResourceID ib) { }
+void Gfx_DeleteIb(GfxResourceID* ib) { }
+
+
+/*########################################################################################################################*
+*------------------------------------------------------Vertex Buffers-----------------------------------------------------*
+*#########################################################################################################################*/
+static cc_uint8* gfx_vertices;
+static int vb_size;
+
+static GfxResourceID Gfx_AllocStaticVb(VertexFormat fmt, int count) {
+	return memalign(16, count * strideSizes[fmt]);
+}
+
+void Gfx_BindVb(GfxResourceID vb) { gfx_vertices = vb; }
+
+void Gfx_DeleteVb(GfxResourceID* vb) {
+	GfxResourceID data = *vb;
+	if (data) Mem_Free(data);
+	*vb = 0;
+}
+
+void* Gfx_LockVb(GfxResourceID vb, VertexFormat fmt, int count) {
+	vb_size = count * strideSizes[fmt];
+	return vb;
+}
+
+void Gfx_UnlockVb(GfxResourceID vb) { 
+	gfx_vertices = vb; 
+	DCFlushRange(vb, vb_size);
+}
+
+
+static GfxResourceID Gfx_AllocDynamicVb(VertexFormat fmt, int maxVertices) {
+	return memalign(16, maxVertices * strideSizes[fmt]);
+}
+
+void Gfx_BindDynamicVb(GfxResourceID vb) { Gfx_BindVb(vb); }
+
+void* Gfx_LockDynamicVb(GfxResourceID vb, VertexFormat fmt, int count) {
+	vb_size = count * strideSizes[fmt];
+	return vb; 
+}
+
+void Gfx_UnlockDynamicVb(GfxResourceID vb) { 
+	gfx_vertices = vb;
+	DCFlushRange(vb, vb_size);
+}
+
+void Gfx_DeleteDynamicVb(GfxResourceID* vb) { Gfx_DeleteVb(vb); }
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------State management----------------------------------------------------*
+*#########################################################################################################################*/
+static PackedCol gfx_fogColor;
+static float gfx_fogEnd = -1.0f, gfx_fogDensity = -1.0f;
+static int gfx_fogMode  = -1;
+
+void Gfx_SetFog(cc_bool enabled) {
+	gfx_fogEnabled = enabled;
+}
+
+void Gfx_SetFogCol(PackedCol color) {
+	if (color == gfx_fogColor) return;
+	gfx_fogColor = color;
+}
+
+void Gfx_SetFogDensity(float value) {
+}
+
+void Gfx_SetFogEnd(float value) {
+	if (value == gfx_fogEnd) return;
+	gfx_fogEnd = value;
+}
+
+void Gfx_SetFogMode(FogFunc func) {
+}
+
+static void SetAlphaTest(cc_bool enabled) {
+	if (enabled) {
+		GX_SetAlphaCompare(GX_GREATER, 127, GX_AOP_AND, GX_ALWAYS, 0);
+	} else {
+		GX_SetAlphaCompare(GX_ALWAYS,    0, GX_AOP_AND, GX_ALWAYS, 0);
+	}
+	
+	// See explanation from libGX headers
+	//   Normally, Z buffering should happen before texturing, as this enables better performance by not texturing pixels that are not visible; 
+	//   however, when alpha compare is used, Z buffering must be done after texturing
+	// Parameter[0] = Enables Z-buffering before texturing when set to GX_TRUE; otherwise, Z-buffering takes place after texturing.
+	GX_SetZCompLoc(!enabled);
+}
+
+void Gfx_DepthOnlyRendering(cc_bool depthOnly) {
+	GX_SetColorUpdate(!depthOnly);
+	GX_SetAlphaUpdate(!depthOnly);
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------Matrices--------------------------------------------------------*
+*#########################################################################################################################*/
+void Gfx_CalcOrthoMatrix(struct Matrix* matrix, float width, float height, float zNear, float zFar) {
+	// Transposed, source guOrtho https://github.com/devkitPro/libogc/blob/master/libogc/gu.c
+	//   The simplified calculation below uses: L = 0, R = width, T = 0, B = height
+	*matrix = Matrix_Identity;
+	
+	matrix->row1.x =  2.0f / width;
+	matrix->row2.y = -2.0f / height;
+	matrix->row3.z = -1.0f / (zFar - zNear);
+
+	matrix->row4.x = -1.0f;
+	matrix->row4.y =  1.0f;
+	matrix->row4.z = -zFar / (zFar - zNear);
+}
+
+static float Cotangent(float x) { return Math_CosF(x) / Math_SinF(x); }
+void Gfx_CalcPerspectiveMatrix(struct Matrix* matrix, float fov, float aspect, float zFar) {
+	float zNear = 0.1f;
+	float c = Cotangent(0.5f * fov);
+	
+	// Transposed, source guPersepctive https://github.com/devkitPro/libogc/blob/master/libogc/gu.c
+	*matrix = Matrix_Identity;
+	matrix->row1.x = c / aspect;
+	matrix->row2.y = c;
+	matrix->row3.z = -(zNear)        / (zFar - zNear);
+	matrix->row3.w = -1.0f;
+	matrix->row4.z = -(zFar * zNear) / (zFar - zNear);
+	matrix->row4.w =  0.0f;
+}
+
+void Gfx_LoadMatrix(MatrixType type, const struct Matrix* matrix) {
+	const float* m = (const float*)matrix;
+	float tmp[16];
+	
+	// Transpose matrix
+	for (int i = 0; i < 4; i++)
+	{
+		tmp[i * 4 + 0] = m[0  + i];
+		tmp[i * 4 + 1] = m[4  + i];
+		tmp[i * 4 + 2] = m[8  + i];
+		tmp[i * 4 + 3] = m[12 + i];
+	}
+		
+	if (type == MATRIX_PROJECTION) {
+		GX_LoadProjectionMtx(tmp,
+			tmp[3*4+3] == 0.0f ? GX_PERSPECTIVE : GX_ORTHOGRAPHIC);
+	} else {
+		GX_LoadPosMtxImm(tmp, GX_PNMTX0);
+	}
+}
+
+void Gfx_LoadIdentityMatrix(MatrixType type) {
+	Gfx_LoadMatrix(type, &Matrix_Identity);
+}
+static float texOffsetX, texOffsetY;
+static void UpdateTexCoordGen(void) {
+	if (texOffsetX || texOffsetY) {
+		Mtx mat   = { 0 };
+		// https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/glTranslate.xml
+		mat[0][0] = 1; mat[0][3] = texOffsetX;
+		mat[1][1] = 1; mat[1][3] = texOffsetY;
+		
+		GX_LoadTexMtxImm(mat, GX_TEXMTX0, GX_MTX2x4);
+		GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX0);
+	} else {
+		GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
+	}
+}
+
+void Gfx_EnableTextureOffset(float x, float y) {
+	texOffsetX = x; texOffsetY = y;
+	UpdateTexCoordGen();
+}
+
+void Gfx_DisableTextureOffset(void) {
+	texOffsetX = 0; texOffsetY = 0;
+	UpdateTexCoordGen();
+}
+
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------Drawing---------------------------------------------------------*
+*#########################################################################################################################*/
+void Gfx_SetVertexFormat(VertexFormat fmt) {
+	if (fmt == gfx_format) return;
+	gfx_format = fmt;
+	gfx_stride = strideSizes[fmt];
+
+	GX_ClearVtxDesc();
+	if (fmt == VERTEX_FORMAT_TEXTURED) {
+		GX_SetVtxDesc(GX_VA_POS,  GX_DIRECT);
+		GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
+		GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
+
+		GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS,  GX_POS_XYZ,  GX_F32,   0);
+		GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
+		GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST,   GX_F32,   0);
+
+		GX_SetNumTexGens(1);
+		// TODO avoid calling here. only call in Gfx_Init and Enable/Disable Texture Offset ???
+		UpdateTexCoordGen();
+		GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
+		GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE);
+	} else {
+		GX_SetVtxDesc(GX_VA_POS,  GX_DIRECT);
+		GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
+
+		GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS,  GX_POS_XYZ,  GX_F32,   0);
+		GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
+
+		GX_SetNumTexGens(0);
+		GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORDNULL, GX_TEXMAP_NULL, GX_COLOR0A0);
+		GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR);
+	}
+}
+
+void Gfx_DrawVb_Lines(int verticesCount) {
+}
+
+
+static void Draw_ColouredTriangles(int verticesCount, int startVertex) {
+	GX_Begin(GX_QUADS, GX_VTXFMT0, verticesCount);
+	// TODO: Ditch indexed rendering and use GX_QUADS instead ??
+	for (int i = 0; i < verticesCount; i++) 
+	{
+		struct VertexColoured* v = (struct VertexColoured*)gfx_vertices + startVertex + i;
+		
+		GX_Position3f32(v->x, v->y, v->z);
+		GX_Color1u32(v->Col);
+	}
+	GX_End();
+}
+
+static void Draw_TexturedTriangles(int verticesCount, int startVertex) {
+	GX_Begin(GX_QUADS, GX_VTXFMT0, verticesCount);
+	for (int i = 0; i < verticesCount; i++) 
+	{
+		struct VertexTextured* v = (struct VertexTextured*)gfx_vertices + startVertex + i;
+		
+		GX_Position3f32(v->x, v->y, v->z);
+		GX_Color1u32(v->Col);
+		GX_TexCoord2f32(v->U, v->V);
+	}
+	GX_End();
+}
+
+void Gfx_DrawVb_IndexedTris_Range(int verticesCount, int startVertex) {
+	if (gfx_format == VERTEX_FORMAT_TEXTURED) {
+		Draw_TexturedTriangles(verticesCount, startVertex);
+	} else {
+		Draw_ColouredTriangles(verticesCount, startVertex);
+	}
+}
+
+void Gfx_DrawVb_IndexedTris(int verticesCount) {
+	if (gfx_format == VERTEX_FORMAT_TEXTURED) {
+		Draw_TexturedTriangles(verticesCount, 0);
+	} else {
+		Draw_ColouredTriangles(verticesCount, 0);
+	}
+}
+
+void Gfx_DrawIndexedTris_T2fC4b(int verticesCount, int startVertex) {
+	Draw_TexturedTriangles(verticesCount, startVertex);
+}
+#endif