summary refs log tree commit diff
path: root/src/Graphics_PS1.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_PS1.c
initial commit
Diffstat (limited to 'src/Graphics_PS1.c')
-rw-r--r--src/Graphics_PS1.c878
1 files changed, 878 insertions, 0 deletions
diff --git a/src/Graphics_PS1.c b/src/Graphics_PS1.c
new file mode 100644
index 0000000..5417dc6
--- /dev/null
+++ b/src/Graphics_PS1.c
@@ -0,0 +1,878 @@
+#include "Core.h"
+#if defined CC_BUILD_PS1
+#include "_GraphicsBase.h"
+#include "Errors.h"
+#include "Window.h"
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <psxgpu.h>
+#include <psxgte.h>
+#include <psxpad.h>
+#include <psxapi.h>
+#include <psxetc.h>
+#include <inline_c.h>
+// Based off https://github.com/Lameguy64/PSn00bSDK/blob/master/examples/beginner/hello/main.c
+
+
+// Length of the ordering table, i.e. the range Z coordinates can have, 0-15 in
+// this case. Larger values will allow for more granularity with depth (useful
+// when drawing a complex 3D scene) at the expense of RAM usage and performance.
+#define OT_LENGTH 1024
+
+// Size of the buffer GPU commands and primitives are written to. If the program
+// crashes due to too many primitives being drawn, increase this value.
+#define BUFFER_LENGTH 32768
+
+typedef struct {
+	DISPENV disp_env;
+	DRAWENV draw_env;
+
+	cc_uint32 ot[OT_LENGTH];
+	cc_uint8  buffer[BUFFER_LENGTH];
+} RenderBuffer;
+
+static RenderBuffer buffers[2];
+static cc_uint8*    next_packet;
+static int          active_buffer;
+static RenderBuffer* buffer;
+static void* lastPoly;
+static cc_bool cullingEnabled;
+
+static void OnBufferUpdated(void) {
+	buffer      = &buffers[active_buffer];
+	next_packet = buffer->buffer;
+	ClearOTagR(buffer->ot, OT_LENGTH);
+}
+
+static void SetupContexts(int w, int h, int r, int g, int b) {
+	SetDefDrawEnv(&buffers[0].draw_env, 0, 0, w, h);
+	SetDefDispEnv(&buffers[0].disp_env, 0, 0, w, h);
+	SetDefDrawEnv(&buffers[1].draw_env, 0, h, w, h);
+	SetDefDispEnv(&buffers[1].disp_env, 0, h, w, h);
+
+	setRGB0(&buffers[0].draw_env, r, g, b);
+	setRGB0(&buffers[1].draw_env, r, g, b);
+	buffers[0].draw_env.isbg = 1;
+	buffers[1].draw_env.isbg = 1;
+
+	active_buffer = 0;
+	OnBufferUpdated();
+}
+
+static void FlipBuffers(void) {
+	DrawSync(0);
+	VSync(0);
+
+	RenderBuffer* draw_buffer = &buffers[active_buffer];
+	RenderBuffer* disp_buffer = &buffers[active_buffer ^ 1];
+
+	PutDispEnv(&disp_buffer->disp_env);
+	DrawOTagEnv(&draw_buffer->ot[OT_LENGTH - 1], &draw_buffer->draw_env);
+
+	active_buffer ^= 1;
+	OnBufferUpdated();
+}
+
+static void* new_primitive(int size) {
+	RenderBuffer* buffer = &buffers[active_buffer];
+	uint8_t* prim        = next_packet;
+
+	next_packet += size;
+
+	assert(next_packet <= &buffer->buffer[BUFFER_LENGTH]);
+	return (void*)prim;
+}
+
+static GfxResourceID white_square;
+void Gfx_RestoreState(void) {
+	InitDefaultResources();
+	
+	// 2x2 dummy white texture
+	struct Bitmap bmp;
+	BitmapCol pixels[4] = { BitmapColor_RGB(255, 0, 0), BITMAPCOLOR_WHITE, BITMAPCOLOR_WHITE, BITMAPCOLOR_WHITE };
+	Bitmap_Init(bmp, 2, 2, pixels);
+	white_square = Gfx_CreateTexture(&bmp, 0, false);
+}
+
+void Gfx_FreeState(void) {
+	FreeDefaultResources(); 
+	Gfx_DeleteTexture(&white_square);
+}
+
+void Gfx_Create(void) {
+	Gfx.MaxTexWidth  = 128;
+	Gfx.MaxTexHeight = 256;
+	Gfx.Created      = true;
+	
+	Gfx_RestoreState();
+	ResetGraph(0);
+
+	SetupContexts(Window_Main.Width, Window_Main.Height, 63, 0, 127);
+	SetDispMask(1);
+
+	InitGeom();
+	//gte_SetGeomOffset(Window_Main.Width / 2, Window_Main.Height / 2);
+	// Set screen depth (basically FOV control, W/2 works best)
+	//gte_SetGeomScreen(Window_Main.Width / 2);
+}
+
+void Gfx_Free(void) { 
+	Gfx_FreeState();
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------Textures--------------------------------------------------------*
+*#########################################################################################################################*/
+// VRAM can be divided into texture pages
+//   32 texture pages total - each page is 64 x 256
+//   10 texture pages are occupied by the doublebuffered display
+//   22 texture pages are usable for textures
+// These 22 pages are then divided into:
+//     - 5 for 128+ wide horizontal, 6 otherwise
+//     - 11 pages for vertical textures
+#define TPAGE_WIDTH   64
+#define TPAGE_HEIGHT 256
+#define TPAGES_PER_HALF 16
+
+// Horizontally oriented textures (first group of 16)
+#define TPAGE_START_HOR 5
+#define MAX_HOR_TEX_PAGES 11
+#define MAX_HOR_TEX_LINES (MAX_HOR_TEX_PAGES * TPAGE_HEIGHT)
+
+// Horizontally oriented textures (second group of 16)
+#define TPAGE_START_VER (16 + 5)
+#define MAX_VER_TEX_PAGES 11
+#define MAX_VER_TEX_LINES (MAX_VER_TEX_PAGES * TPAGE_WIDTH)
+
+static cc_uint8 vram_used[(MAX_HOR_TEX_LINES + MAX_VER_TEX_LINES) / 8];
+#define VRAM_SetUsed(line) (vram_used[(line) / 8] |=  (1 << ((line) % 8)))
+#define VRAM_UnUsed(line)  (vram_used[(line) / 8] &= ~(1 << ((line) % 8)))
+#define VRAM_IsUsed(line)  (vram_used[(line) / 8] &   (1 << ((line) % 8)))
+
+#define VRAM_BoundingAxis(width, height) height > width ? width : height
+
+static void VRAM_GetBlockRange(int width, int height, int* beg, int* end) {
+	if (height > width) {
+		*beg = MAX_HOR_TEX_LINES;
+		*end = MAX_HOR_TEX_LINES + MAX_VER_TEX_LINES;
+	} else if (width >= 128) {
+		*beg = 0;
+		*end = 5 * TPAGE_HEIGHT;
+	} else {
+		*beg = 5 * TPAGE_HEIGHT;
+		*end = MAX_HOR_TEX_LINES;
+	}
+}
+
+static cc_bool VRAM_IsRangeFree(int beg, int end) {
+	for (int i = beg; i < end; i++) 
+	{
+		if (VRAM_IsUsed(i)) return false;
+	}
+	return true;
+}
+
+static int VRAM_FindFreeBlock(int width, int height) {
+	int beg, end;
+	VRAM_GetBlockRange(width, height, &beg, &end);
+	
+	// TODO kinda inefficient
+	for (int i = beg; i < end - height; i++) 
+	{
+		if (VRAM_IsUsed(i)) continue;
+		
+		if (VRAM_IsRangeFree(i, i + height)) return i;
+	}
+	return -1;
+}
+
+static void VRAM_AllocBlock(int line, int width, int height) {
+	int lines = VRAM_BoundingAxis(width, height);
+	for (int i = line; i < line + lines; i++) 
+	{
+		VRAM_SetUsed(i);
+	}
+}
+
+static void VRAM_FreeBlock(int line, int width, int height) {
+	int lines = VRAM_BoundingAxis(width, height);
+	for (int i = line; i < line + lines; i++) 
+	{
+		VRAM_UnUsed(i);
+	}
+}
+
+static int VRAM_CalcPage(int line) {
+	if (line < MAX_HOR_TEX_LINES) {
+		return TPAGE_START_HOR + (line / TPAGE_HEIGHT);
+	} else {
+		line -= MAX_HOR_TEX_LINES;
+		return TPAGE_START_VER + (line / TPAGE_WIDTH);
+	}
+}
+
+
+#define TEXTURES_MAX_COUNT 64
+typedef struct GPUTexture {
+	cc_uint16 width, height;
+	cc_uint8 width_mask, height_mask;
+	cc_uint16 line, tpage;
+	cc_uint8 xOffset, yOffset;
+} GPUTexture;
+static GPUTexture textures[TEXTURES_MAX_COUNT];
+static GPUTexture* curTex;
+
+#define BGRA8_to_PS1(src) \
+	((src[2] & 0xF8) >> 3) | ((src[1] & 0xF8) << 2) | ((src[0] & 0xF8) << 7) | ((src[3] & 0x80) << 8)
+
+static void* AllocTextureAt(int i, struct Bitmap* bmp, int rowWidth) {
+	cc_uint16* tmp = Mem_TryAlloc(bmp->width * bmp->height, 2);
+	if (!tmp) return NULL;
+
+	for (int y = 0; y < bmp->height; y++)
+	{
+		cc_uint32* src = bmp->scan0 + y * rowWidth;
+		cc_uint16* dst = tmp        + y * bmp->width;
+		
+		for (int x = 0; x < bmp->width; x++) {
+			cc_uint8* color = (cc_uint8*)&src[x];
+			dst[x] = BGRA8_to_PS1(color);
+		}
+	}
+
+	GPUTexture* tex = &textures[i];
+	int line = VRAM_FindFreeBlock(bmp->width, bmp->height);
+	if (line == -1) { Mem_Free(tmp); return NULL; }
+	
+	tex->width  = bmp->width;  tex->width_mask  = bmp->width  - 1;
+	tex->height = bmp->height; tex->height_mask = bmp->height - 1;
+	tex->line   = line;
+	
+	int page   = VRAM_CalcPage(line);
+	int pageX  = (page % TPAGES_PER_HALF);
+	int pageY  = (page / TPAGES_PER_HALF);
+	tex->tpage = (2 << 7) | (pageY << 4) | pageX;
+
+	VRAM_AllocBlock(line, bmp->width, bmp->height);
+	if (bmp->height > bmp->width) {
+		tex->xOffset = line % TPAGE_WIDTH;
+		tex->yOffset = 0;
+	} else {
+		tex->xOffset = 0;
+		tex->yOffset = line % TPAGE_HEIGHT;
+	}
+	
+	Platform_Log3("%i x %i  = %i", &bmp->width, &bmp->height, &line);
+	Platform_Log3("  at %i (%i, %i)", &page, &pageX, &pageY);
+		
+	RECT rect;
+	rect.x = pageX * TPAGE_WIDTH  + tex->xOffset;
+	rect.y = pageY * TPAGE_HEIGHT + tex->yOffset;
+	rect.w = bmp->width;
+	rect.h = bmp->height;
+
+	int RX = rect.x, RY = rect.y;
+	Platform_Log2("  LOAD AT: %i, %i", &RX, &RY);
+	LoadImage2(&rect, tmp);
+	
+	Mem_Free(tmp);
+	return tex;
+}
+
+static GfxResourceID Gfx_AllocTexture(struct Bitmap* bmp, int rowWidth, cc_uint8 flags, cc_bool mipmaps) {
+	for (int i = 0; i < TEXTURES_MAX_COUNT; i++)
+	{
+		if (textures[i].width) continue;
+		return AllocTextureAt(i, bmp, rowWidth);
+	}
+
+	Platform_LogConst("No room for more textures");
+	return NULL;
+}
+
+void Gfx_BindTexture(GfxResourceID texId) {
+	if (!texId) texId = white_square;
+	curTex = (GPUTexture*)texId;
+}
+		
+void Gfx_DeleteTexture(GfxResourceID* texId) {
+	GfxResourceID data = *texId;
+	if (!data) return;
+	GPUTexture* tex = (GPUTexture*)data;
+
+	VRAM_FreeBlock(tex->line, tex->width, tex->height);
+	tex->width = 0; tex->height = 0;
+	*texId = NULL;
+}
+
+void Gfx_UpdateTexture(GfxResourceID texId, int x, int y, struct Bitmap* part, int rowWidth, cc_bool mipmaps) {
+	// TODO
+}
+
+void Gfx_EnableMipmaps(void)  { }
+void Gfx_DisableMipmaps(void) { }
+
+
+/*########################################################################################################################*
+*------------------------------------------------------State management---------------------------------------------------*
+*#########################################################################################################################*/
+void Gfx_SetFog(cc_bool enabled)    { }
+void Gfx_SetFogCol(PackedCol col)   { }
+void Gfx_SetFogDensity(float value) { }
+void Gfx_SetFogEnd(float value)     { }
+void Gfx_SetFogMode(FogFunc func)   { }
+
+void Gfx_SetFaceCulling(cc_bool enabled) {
+	cullingEnabled = enabled;
+}
+
+static void SetAlphaTest(cc_bool enabled) {
+}
+
+static void SetAlphaBlend(cc_bool enabled) {
+}
+
+void Gfx_SetAlphaArgBlend(cc_bool enabled) { }
+
+void Gfx_ClearBuffers(GfxBuffers buffers) {
+}
+
+void Gfx_ClearColor(PackedCol color) {
+	int r = PackedCol_R(color);
+	int g = PackedCol_G(color);
+	int b = PackedCol_B(color);
+
+	setRGB0(&buffers[0].draw_env, r, g, b);
+	setRGB0(&buffers[1].draw_env, r, g, b);
+}
+
+void Gfx_SetDepthTest(cc_bool enabled) {
+}
+
+void Gfx_SetDepthWrite(cc_bool enabled) {
+	// TODO
+}
+
+static void SetColorWrite(cc_bool r, cc_bool g, cc_bool b, cc_bool a) {
+	// TODO
+}
+
+void Gfx_DepthOnlyRendering(cc_bool depthOnly) {
+	cc_bool enabled = !depthOnly;
+	SetColorWrite(enabled & gfx_colorMask[0], enabled & gfx_colorMask[1], 
+				  enabled & gfx_colorMask[2], enabled & gfx_colorMask[3]);
+}
+
+
+/*########################################################################################################################*
+*-------------------------------------------------------Index buffers-----------------------------------------------------*
+*#########################################################################################################################*/
+GfxResourceID Gfx_CreateIb2(int count, Gfx_FillIBFunc fillFunc, void* obj) {
+	return (void*)1;
+}
+
+void Gfx_BindIb(GfxResourceID ib) { }
+void Gfx_DeleteIb(GfxResourceID* ib) { }
+
+
+/*########################################################################################################################*
+*-------------------------------------------------------Vertex buffers----------------------------------------------------*
+*#########################################################################################################################*/
+static void* gfx_vertices;
+
+static GfxResourceID Gfx_AllocStaticVb(VertexFormat fmt, int count) {
+	return Mem_TryAlloc(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) {
+	return vb;
+}
+
+void Gfx_UnlockVb(GfxResourceID vb) { 
+	gfx_vertices = vb; 
+}
+
+
+static GfxResourceID Gfx_AllocDynamicVb(VertexFormat fmt, int maxVertices) {
+	return Mem_TryAlloc(maxVertices, strideSizes[fmt]);
+}
+
+void Gfx_BindDynamicVb(GfxResourceID vb) { Gfx_BindVb(vb); }
+
+void* Gfx_LockDynamicVb(GfxResourceID vb, VertexFormat fmt, int count) {
+	return vb; 
+}
+
+void Gfx_UnlockDynamicVb(GfxResourceID vb) { 
+	gfx_vertices = vb;
+}
+
+void Gfx_DeleteDynamicVb(GfxResourceID* vb) { Gfx_DeleteVb(vb); }
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------Matrices--------------------------------------------------------*
+*#########################################################################################################################*/
+static struct Matrix _view, _proj, mvp;
+#define ToFixed(v) (int)(v * (1 << 12))
+
+static void LoadTransformMatrix(struct Matrix* src) {
+	// https://math.stackexchange.com/questions/237369/given-this-transformation-matrix-how-do-i-decompose-it-into-translation-rotati
+	MATRIX mtx;
+
+	mtx.t[0] = 0;
+	mtx.t[1] = 0;
+	mtx.t[2] = 0;
+
+	//Platform_Log3("X: %f3, Y: %f3, Z: %f3", &src->row1.x, &src->row1.y, &src->row1.z);
+	//Platform_Log3("X: %f3, Y: %f3, Z: %f3", &src->row2.x, &src->row2.y, &src->row2.z);
+	//Platform_Log3("X: %f3, Y: %f3, Z: %f3", &src->row3.x, &src->row3.y, &src->row3.z);
+	//Platform_Log3("X: %f3, Y: %f3, Z: %f3", &src->row4.x, &src->row4.y, &src->row4.z);
+	//Platform_LogConst("====");
+
+	float len1 = Math_SqrtF(src->row1.x + src->row1.y + src->row1.z);
+	float len2 = Math_SqrtF(src->row2.x + src->row2.y + src->row2.z);
+	float len3 = Math_SqrtF(src->row3.x + src->row3.y + src->row3.z);
+
+	mtx.m[0][0] = ToFixed(1);
+	mtx.m[0][1] = 0;
+	mtx.m[0][2] = 0;
+
+	mtx.m[1][0] = 0;
+	mtx.m[1][1] = ToFixed(1);
+	mtx.m[1][2] = 0;
+
+	mtx.m[2][0] = 0;
+	mtx.m[2][1] = ToFixed(1);
+	mtx.m[2][2] = 1;
+	
+	gte_SetRotMatrix(&mtx);
+	gte_SetTransMatrix(&mtx);
+}
+
+/*static void LoadTransformMatrix(struct Matrix* src) {
+	// https://math.stackexchange.com/questions/237369/given-this-transformation-matrix-how-do-i-decompose-it-into-translation-rotati
+	MATRIX mtx;
+
+	mtx.t[0] = ToFixed(src->row4.x);
+	mtx.t[1] = ToFixed(src->row4.y);
+	mtx.t[2] = ToFixed(src->row4.z);
+
+	Platform_Log3("X: %f3, Y: %f3, Z: %f3", &src->row1.x, &src->row1.y, &src->row1.z);
+	Platform_Log3("X: %f3, Y: %f3, Z: %f3", &src->row2.x, &src->row2.y, &src->row2.z);
+	Platform_Log3("X: %f3, Y: %f3, Z: %f3", &src->row3.x, &src->row3.y, &src->row3.z);
+	Platform_Log3("X: %f3, Y: %f3, Z: %f3", &src->row4.x, &src->row4.y, &src->row4.z);
+	Platform_LogConst("====");
+
+	float len1 = Math_SqrtF(src->row1.x + src->row1.y + src->row1.z);
+	float len2 = Math_SqrtF(src->row2.x + src->row2.y + src->row2.z);
+	float len3 = Math_SqrtF(src->row3.x + src->row3.y + src->row3.z);
+
+	mtx.m[0][0] = ToFixed(src->row1.x / len1);
+	mtx.m[0][1] = ToFixed(src->row1.y / len1);
+	mtx.m[0][2] = ToFixed(src->row1.z / len1);
+
+	mtx.m[1][0] = ToFixed(src->row2.x / len2);
+	mtx.m[1][1] = ToFixed(src->row2.y / len2);
+	mtx.m[1][2] = ToFixed(src->row2.z / len2);
+
+	mtx.m[2][0] = ToFixed(src->row3.x / len3);
+	mtx.m[2][1] = ToFixed(src->row3.y / len3);
+	mtx.m[2][2] = ToFixed(src->row3.z / len3);
+	
+	gte_SetRotMatrix(&mtx);
+	gte_SetTransMatrix(&mtx);
+}*/
+
+void Gfx_LoadMatrix(MatrixType type, const struct Matrix* matrix) {
+	if (type == MATRIX_VIEW)       _view = *matrix;
+	if (type == MATRIX_PROJECTION) _proj = *matrix;
+
+	Matrix_Mul(&mvp, &_view, &_proj);
+	LoadTransformMatrix(&mvp);
+}
+
+void Gfx_LoadIdentityMatrix(MatrixType type) {
+	Gfx_LoadMatrix(type, &Matrix_Identity);
+}
+
+void Gfx_EnableTextureOffset(float x, float y) {
+	// TODO
+}
+
+void Gfx_DisableTextureOffset(void) {
+	// TODO
+}
+
+void Gfx_CalcOrthoMatrix(struct Matrix* matrix, float width, float height, float zNear, float zFar) {
+	/* Source https://learn.microsoft.com/en-us/windows/win32/direct3d9/d3dxmatrixorthooffcenterrh */
+	/*   The simplified calculation below uses: L = 0, R = width, T = 0, B = height */
+	/* NOTE: This calculation is shared with Direct3D 11 backend */
+	*matrix = Matrix_Identity;
+
+	matrix->row1.x =  2.0f / width;
+	matrix->row2.y = -2.0f / height;
+	matrix->row3.z =  1.0f / (zNear - zFar);
+
+	matrix->row4.x = -1.0f;
+	matrix->row4.y =  1.0f;
+	matrix->row4.z = zNear / (zNear - zFar);
+}
+
+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.01f;
+	/* Source https://learn.microsoft.com/en-us/windows/win32/direct3d9/d3dxmatrixperspectivefovrh */
+	float c = (float)Cotangent(0.5f * fov);
+	*matrix = Matrix_Identity;
+
+	matrix->row1.x =  c / aspect;
+	matrix->row2.y =  c;
+	matrix->row3.z = zFar / (zNear - zFar);
+	matrix->row3.w = -1.0f;
+	matrix->row4.z = (zNear * zFar) / (zNear - zFar);
+	matrix->row4.w =  0.0f;
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------Rendering-------------------------------------------------------*
+*#########################################################################################################################*/
+void Gfx_SetVertexFormat(VertexFormat fmt) {
+	gfx_format = fmt;
+	gfx_stride = strideSizes[fmt];
+}
+
+void Gfx_DrawVb_Lines(int verticesCount) {
+
+}
+
+static void Transform(Vec3* result, struct VertexTextured* a, const struct Matrix* mat) {
+	/* a could be pointing to result - therefore can't directly assign X/Y/Z */
+	float x = a->x * mat->row1.x + a->y * mat->row2.x + a->z * mat->row3.x + mat->row4.x;
+	float y = a->x * mat->row1.y + a->y * mat->row2.y + a->z * mat->row3.y + mat->row4.y;
+	float z = a->x * mat->row1.z + a->y * mat->row2.z + a->z * mat->row3.z + mat->row4.z;
+	float w = a->x * mat->row1.w + a->y * mat->row2.w + a->z * mat->row3.w + mat->row4.w;
+	
+	result->x = (x/w) *  (320/2) + (320/2); 
+	result->y = (y/w) * -(240/2) + (240/2);
+	result->z = (z/w) * OT_LENGTH;
+}
+
+cc_bool VERTEX_LOGGING;
+
+static void DrawColouredQuads2D(int verticesCount, int startVertex) {
+	return;
+	for (int i = 0; i < verticesCount; i += 4) 
+	{
+		struct VertexColoured* v = (struct VertexColoured*)gfx_vertices + startVertex + i;
+		
+		POLY_F4* poly = new_primitive(sizeof(POLY_F4));
+		setPolyF4(poly);
+
+		poly->x0 = v[1].x; poly->y0 = v[1].y;
+		poly->x1 = v[0].x; poly->y1 = v[0].y;
+		poly->x2 = v[2].x; poly->y2 = v[2].y;
+		poly->x3 = v[3].x; poly->y3 = v[3].y;
+
+		poly->r0 = PackedCol_R(v->Col);
+		poly->g0 = PackedCol_G(v->Col);
+		poly->b0 = PackedCol_B(v->Col);
+
+		if (lastPoly) { 
+			setaddr(poly, getaddr(lastPoly)); setaddr(lastPoly, poly); 
+		} else {
+			addPrim(&buffer->ot[0], poly);
+		}
+		lastPoly = poly;
+	}
+}
+
+static void DrawTexturedQuads2D(int verticesCount, int startVertex) {
+	int uOffset = curTex->xOffset, vOffset = curTex->yOffset;
+
+	for (int i = 0; i < verticesCount; i += 4) 
+	{
+		struct VertexTextured* v = (struct VertexTextured*)gfx_vertices + startVertex + i;
+		
+		POLY_FT4* poly = new_primitive(sizeof(POLY_FT4));
+		setPolyFT4(poly);
+		poly->tpage = curTex->tpage;
+		poly->clut  = 0;
+
+		// TODO & instead of % 
+		poly->x0 = v[1].x; poly->y0 = v[1].y; 
+		poly->x1 = v[0].x; poly->y1 = v[0].y; 
+		poly->x2 = v[2].x; poly->y2 = v[2].y; 
+		poly->x3 = v[3].x; poly->y3 = v[3].y; 
+		
+		poly->u0 = ((int)(v[1].U * 0.99f * curTex->width)  & curTex->width_mask)  + uOffset;
+		poly->v0 = ((int)(v[1].V         * curTex->height) & curTex->height_mask) + vOffset;
+		poly->u1 = ((int)(v[0].U * 0.99f * curTex->width)  & curTex->width_mask)  + uOffset;
+		poly->v1 = ((int)(v[0].V         * curTex->height) & curTex->height_mask) + vOffset;
+		poly->u2 = ((int)(v[2].U * 0.99f * curTex->width)  & curTex->width_mask)  + uOffset;
+		poly->v2 = ((int)(v[2].V         * curTex->height) & curTex->height_mask) + vOffset;
+		poly->u3 = ((int)(v[3].U * 0.99f * curTex->width)  & curTex->width_mask)  + uOffset;
+		poly->v3 = ((int)(v[3].V         * curTex->height) & curTex->height_mask) + vOffset;
+
+		// https://problemkaputt.de/psxspx-gpu-rendering-attributes.htm
+		// "For untextured graphics, 8bit RGB values of FFh are brightest. However, for texture blending, 8bit values of 80h are brightest"
+		poly->r0 = PackedCol_R(v->Col) >> 1;
+		poly->g0 = PackedCol_G(v->Col) >> 1;
+		poly->b0 = PackedCol_B(v->Col) >> 1;
+
+		if (lastPoly) { 
+			setaddr(poly, getaddr(lastPoly)); setaddr(lastPoly, poly); 
+		} else {
+			addPrim(&buffer->ot[0], poly);
+		}
+		lastPoly = poly;
+	}
+}
+
+static void DrawColouredQuads3D(int verticesCount, int startVertex) {
+	for (int i = 0; i < verticesCount; i += 4) 
+	{
+		struct VertexColoured* v = (struct VertexColoured*)gfx_vertices + startVertex + i;
+		
+		POLY_F4* poly = new_primitive(sizeof(POLY_F4));
+		setPolyF4(poly);
+
+		Vec3 coords[4];
+		Transform(&coords[0], &v[0], &mvp);
+		Transform(&coords[1], &v[1], &mvp);
+		Transform(&coords[2], &v[2], &mvp);
+		Transform(&coords[3], &v[3], &mvp);
+
+		poly->x0 = coords[1].x; poly->y0 = coords[1].y;
+		poly->x1 = coords[0].x; poly->y1 = coords[0].y;
+		poly->x2 = coords[2].x; poly->y2 = coords[2].y;
+		poly->x3 = coords[3].x; poly->y3 = coords[3].y;
+
+		int p = (coords[0].z + coords[1].z + coords[2].z + coords[3].z) / 4;
+		if (p < 0 || p >= OT_LENGTH) continue;
+
+		int X = v[0].x, Y = v[0].y, Z = v[0].z;
+		//if (VERTEX_LOGGING) Platform_Log3("IN: %i, %i, %i", &X, &Y, &Z);
+		X = poly->x1; Y = poly->y1, Z = coords[0].z;
+
+		poly->r0 = PackedCol_R(v->Col);
+		poly->g0 = PackedCol_G(v->Col);
+		poly->b0 = PackedCol_B(v->Col);
+		//if (VERTEX_LOGGING) Platform_Log4("OUT: %i, %i, %i (%i)", &X, &Y, &Z, &p);
+
+		addPrim(&buffer->ot[p >> 2], poly);
+	}
+}
+
+static void DrawTexturedQuads3D(int verticesCount, int startVertex) {
+	int uOffset = curTex->xOffset, vOffset = curTex->yOffset;
+
+	for (int i = 0; i < verticesCount; i += 4) 
+	{
+		struct VertexTextured* v = (struct VertexTextured*)gfx_vertices + startVertex + i;
+		
+		POLY_FT4* poly = new_primitive(sizeof(POLY_FT4));
+		setPolyFT4(poly);
+		poly->tpage = curTex->tpage;
+		poly->clut  = 0;
+
+		Vec3 coords[4];
+		Transform(&coords[0], &v[0], &mvp);
+		Transform(&coords[1], &v[1], &mvp);
+		Transform(&coords[2], &v[2], &mvp);
+		Transform(&coords[3], &v[3], &mvp);
+
+		// TODO & instead of % 
+		poly->x0 = coords[1].x; poly->y0 = coords[1].y;
+		poly->x1 = coords[0].x; poly->y1 = coords[0].y;
+		poly->x2 = coords[2].x; poly->y2 = coords[2].y;
+		poly->x3 = coords[3].x; poly->y3 = coords[3].y;
+		
+		if (cullingEnabled) {
+			// https://gamedev.stackexchange.com/questions/203694/how-to-make-backface-culling-work-correctly-in-both-orthographic-and-perspective
+			int signA = (poly->x0 - poly->x1) * (poly->y2 - poly->y1);
+			int signB = (poly->x2 - poly->x1) * (poly->y0 - poly->y1);
+			if (signA > signB) continue;
+		}
+		
+		poly->u0 = ((int)(v[1].U * curTex->width)  & curTex->width_mask)  + uOffset;
+		poly->v0 = ((int)(v[1].V * curTex->height) & curTex->height_mask) + vOffset;
+		poly->u1 = ((int)(v[0].U * curTex->width)  & curTex->width_mask)  + uOffset;
+		poly->v1 = ((int)(v[0].V * curTex->height) & curTex->height_mask) + vOffset;
+		poly->u2 = ((int)(v[2].U * curTex->width)  & curTex->width_mask)  + uOffset;
+		poly->v2 = ((int)(v[2].V * curTex->height) & curTex->height_mask) + vOffset;
+		poly->u3 = ((int)(v[3].U * curTex->width)  & curTex->width_mask)  + uOffset;
+		poly->v3 = ((int)(v[3].V * curTex->height) & curTex->height_mask) + vOffset;
+		
+		//int P = curTex->height, page = poly->tpage & 0xFF, ll = curTex->yOffset;
+		//Platform_Log4("XYZ: %f3 x %i, %i, %i", &v[0].V, &P, &page, &ll);
+		int p = (coords[0].z + coords[1].z + coords[2].z + coords[3].z) / 4;
+		if (p < 0 || p >= OT_LENGTH) continue;
+
+		int X = v[0].x, Y = v[0].y, Z = v[0].z;
+		//if (VERTEX_LOGGING) Platform_Log3("IN: %i, %i, %i", &X, &Y, &Z);
+		X = poly->x1; Y = poly->y1, Z = coords[0].z;
+
+		poly->r0 = PackedCol_R(v->Col) >> 1;
+		poly->g0 = PackedCol_G(v->Col) >> 1;
+		poly->b0 = PackedCol_B(v->Col) >> 1;
+		//if (VERTEX_LOGGING) Platform_Log4("OUT: %i, %i, %i (%i)", &X, &Y, &Z, &p);
+
+		// TODO: 2D shouldn't use AddPrim, draws in the wrong way
+		addPrim(&buffer->ot[p >> 2], poly);
+	}
+}
+
+/*static void DrawQuads(int verticesCount, int startVertex) {
+	for (int i = 0; i < verticesCount; i += 4) 
+	{
+		struct VertexTextured* v = (struct VertexTextured*)gfx_vertices + startVertex + i;
+		
+		POLY_F4* poly = new_primitive(sizeof(POLY_F4));
+		setPolyF4(poly);
+
+		SVECTOR coords[4];
+		coords[0].vx = v[0].x; coords[0].vy = v[0].y; coords[0].vz = v[0].z;
+		coords[1].vx = v[1].x; coords[1].vy = v[1].y; coords[1].vz = v[1].z;
+		coords[2].vx = v[2].x; coords[2].vy = v[2].y; coords[2].vz = v[1].z;
+		coords[3].vx = v[3].x; coords[3].vy = v[3].y; coords[3].vz = v[3].z;
+
+		int X = coords[0].vx, Y = coords[0].vy, Z = coords[0].vz;
+		//Platform_Log3("IN: %i, %i, %i", &X, &Y, &Z);
+		gte_ldv3(&coords[0], &coords[1], &coords[2]);
+		gte_rtpt();
+		gte_stsxy0(&poly->x0);
+
+		int p;
+		gte_avsz3();
+		gte_stotz( &p );
+
+		X = poly->x0; Y = poly->y0, Z = p;
+		//Platform_Log3("OUT: %i, %i, %i", &X, &Y, &Z);
+		if (((p >> 2) >= OT_LENGTH) || ((p >> 2) < 0))
+			continue;
+
+		gte_ldv0(&coords[3]);
+		gte_rtps();
+		gte_stsxy3(&poly->x1, &poly->x2, &poly->x3);
+
+		//poly->x0 = v[1].x; poly->y0 = v[1].y;
+		//poly->x1 = v[0].x; poly->y1 = v[0].y;
+		//poly->x2 = v[2].x; poly->y2 = v[2].y;
+		//poly->x3 = v[3].x; poly->y3 = v[3].y;
+
+		poly->r0 = PackedCol_R(v->Col);
+		poly->g0 = PackedCol_G(v->Col);
+		poly->b0 = PackedCol_B(v->Col);
+
+		addPrim(&buffer->ot[p >> 2], poly);
+	}
+}*/
+
+/*static void DrawQuads(int verticesCount, int startVertex) {
+	for (int i = 0; i < verticesCount; i += 4) 
+	{
+		struct VertexTextured* v = (struct VertexTextured*)gfx_vertices + startVertex + i;
+		
+		POLY_F4* poly = new_primitive(sizeof(POLY_F4));
+		setPolyF4(poly);
+
+		poly->x0 = v[1].x; poly->y0 = v[1].y;
+		poly->x1 = v[0].x; poly->y1 = v[0].y;
+		poly->x2 = v[2].x; poly->y2 = v[2].y;
+		poly->x3 = v[3].x; poly->y3 = v[3].y;
+
+		poly->r0 = PackedCol_R(v->Col);
+		poly->g0 = PackedCol_G(v->Col);
+		poly->b0 = PackedCol_B(v->Col);
+
+		int p = 0;
+		addPrim(&buffer->ot[p >> 2], poly);
+	}
+}*/
+
+static void DrawQuads(int verticesCount, int startVertex) {
+	if (gfx_rendering2D && gfx_format == VERTEX_FORMAT_TEXTURED) {
+		DrawTexturedQuads2D(verticesCount, startVertex);
+	} else if (gfx_rendering2D) {
+		DrawColouredQuads2D(verticesCount, startVertex);
+	} else if (gfx_format == VERTEX_FORMAT_TEXTURED) {
+		DrawTexturedQuads3D(verticesCount, startVertex);
+	} else {
+		DrawColouredQuads3D(verticesCount, startVertex);
+	}
+}
+
+
+void Gfx_DrawVb_IndexedTris_Range(int verticesCount, int startVertex) {
+	DrawQuads(verticesCount, startVertex);
+}
+
+void Gfx_DrawVb_IndexedTris(int verticesCount) {
+	DrawQuads(verticesCount, 0);
+}
+
+void Gfx_DrawIndexedTris_T2fC4b(int verticesCount, int startVertex) {
+	DrawTexturedQuads3D(verticesCount, startVertex);
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------Other/Misc------------------------------------------------------*
+*#########################################################################################################################*/
+cc_result Gfx_TakeScreenshot(struct Stream* output) {
+	return ERR_NOT_SUPPORTED;
+}
+
+cc_bool Gfx_WarnIfNecessary(void) {
+	return false;
+}
+
+void Gfx_BeginFrame(void) {
+	lastPoly = NULL;
+}
+
+void Gfx_EndFrame(void) {
+	FlipBuffers();
+	if (gfx_minFrameMs) LimitFPS();
+}
+
+void Gfx_SetFpsLimit(cc_bool vsync, float minFrameMs) {
+	gfx_minFrameMs = minFrameMs;
+	gfx_vsync      = vsync;
+}
+
+void Gfx_OnWindowResize(void) {
+	// TODO
+}
+
+void Gfx_SetViewport(int x, int y, int w, int h) { }
+
+void Gfx_GetApiInfo(cc_string* info) {
+	String_AppendConst(info, "-- Using PS1 --\n");
+	PrintMaxTextureInfo(info);
+}
+
+cc_bool Gfx_TryRestoreContext(void) { return true; }
+
+void Gfx_Begin2D(int width, int height) {
+	gfx_rendering2D = true;
+	Gfx_SetAlphaBlending(true);
+}
+
+void Gfx_End2D(void) {
+	gfx_rendering2D = false;
+	Gfx_SetAlphaBlending(false);
+}
+#endif