diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Graphics_N64.c |
initial commit
Diffstat (limited to 'src/Graphics_N64.c')
-rw-r--r-- | src/Graphics_N64.c | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/src/Graphics_N64.c b/src/Graphics_N64.c new file mode 100644 index 0000000..23c1911 --- /dev/null +++ b/src/Graphics_N64.c @@ -0,0 +1,491 @@ +#include "Core.h" +#ifdef CC_BUILD_N64 +#include "_GraphicsBase.h" +#include "Errors.h" +#include "Logger.h" +#include "Window.h" +#include <libdragon.h> +#include <GL/gl.h> +#include <GL/gl_integration.h> + +typedef void (*GL_SetupVBFunc)(void); +typedef void (*GL_SetupVBRangeFunc)(int startVertex); +static GL_SetupVBFunc gfx_setupVBFunc; +static GL_SetupVBRangeFunc gfx_setupVBRangeFunc; + + +/*########################################################################################################################* +*---------------------------------------------------------General---------------------------------------------------------* +*#########################################################################################################################*/ +static void GL_UpdateVsync(void) { + //GLContext_SetFpsLimit(gfx_vsync, gfx_minFrameMs); +} +static surface_t zbuffer; + +void Gfx_Create(void) { + gl_init(); + //rdpq_debug_start(); // TODO debug + //rdpq_debug_log(true); + zbuffer = surface_alloc(FMT_RGBA16, display_get_width(), display_get_height()); + + Gfx.MaxTexWidth = 256; + Gfx.MaxTexHeight = 256; + Gfx.Created = true; + + // TMEM only has 4 KB in it, which can be interpreted as + // - 1024 32bpp pixels + // - 2048 16bpp pixels + Gfx.MaxTexSize = 1024; + Gfx.MaxLowResTexSize = 2048; + + Gfx.SupportsNonPowTwoTextures = true; + Gfx_RestoreState(); + GL_UpdateVsync(); +} + +cc_bool Gfx_TryRestoreContext(void) { + return true; +} + +void Gfx_Free(void) { + Gfx_FreeState(); + gl_close(); +} + +#define gl_Toggle(cap) if (enabled) { glEnable(cap); } else { glDisable(cap); } + + +/*########################################################################################################################* +*-----------------------------------------------------------Misc----------------------------------------------------------* +*#########################################################################################################################*/ +static color_t gfx_clearColor; + +cc_result Gfx_TakeScreenshot(struct Stream* output) { + return ERR_NOT_SUPPORTED; +} + +void Gfx_GetApiInfo(cc_string* info) { + String_AppendConst(info, "-- Using Nintendo 64 --\n"); + String_AppendConst(info, "GPU: Nintendo 64 RDP (LibDragon OpenGL)\n"); + String_AppendConst(info, "T&L: Nintendo 64 RSP (LibDragon OpenGL)\n"); + PrintMaxTextureInfo(info); +} + +void Gfx_SetFpsLimit(cc_bool vsync, float minFrameMs) { + gfx_minFrameMs = minFrameMs; + gfx_vsync = vsync; + if (Gfx.Created) GL_UpdateVsync(); +} + +void Gfx_OnWindowResize(void) { } + +void Gfx_SetViewport(int x, int y, int w, int h) { } + + +void Gfx_BeginFrame(void) { + surface_t* disp = display_get(); + rdpq_attach(disp, &zbuffer); + + gl_context_begin(); + Platform_LogConst("GFX ctx beg"); +} + +extern void __rdpq_autosync_change(int mode); +static color_t gfx_clearColor; + +void Gfx_ClearBuffers(GfxBuffers buffers) { + __rdpq_autosync_change(AUTOSYNC_PIPE); + + if (buffers & GFX_BUFFER_COLOR) + rdpq_clear(gfx_clearColor); + if (buffers & GFX_BUFFER_DEPTH) + rdpq_clear_z(0xFFFC); +} + +void Gfx_ClearColor(PackedCol color) { + gfx_clearColor.r = PackedCol_R(color); + gfx_clearColor.g = PackedCol_G(color); + gfx_clearColor.b = PackedCol_B(color); + gfx_clearColor.a = PackedCol_A(color); + //memcpy(&gfx_clearColor, &color, sizeof(color_t)); // TODO maybe use proper conversion from graphics.h ?? +} + +void Gfx_EndFrame(void) { + Platform_LogConst("GFX ctx end"); + gl_context_end(); + rdpq_detach_show(); + + if (gfx_minFrameMs) LimitFPS(); +//Platform_LogConst("GFX END"); +} + + +/*########################################################################################################################* +*---------------------------------------------------------Textures--------------------------------------------------------* +*#########################################################################################################################*/ +typedef struct CCTexture { + surface_t surface; + GLuint textureID; +} CCTexture; + +#define ALIGNUP8(size) (((size) + 7) & ~0x07) + +// A8 B8 G8 R8 > A1 B5 G5 B5 +#define To16BitPixel(src) \ + ((src & 0x80) >> 7) | ((src & 0xF800) >> 10) | ((src & 0xF80000) >> 13) | ((src & 0xF8000000) >> 16); + +static GfxResourceID Gfx_AllocTexture(struct Bitmap* bmp, int rowWidth, cc_uint8 flags, cc_bool mipmaps) { + cc_bool bit16 = flags & TEXTURE_FLAG_LOWRES; + // rows are actually 8 byte aligned in TMEM https://github.com/DragonMinded/libdragon/blob/f360fa1bb1fb3ff3d98f4ab58692d40c828636c9/src/rdpq/rdpq_tex.c#L132 + // so even though width * height * pixel size may fit within 4096 bytes, after adjusting for 8 byte alignment, row pitch * height may exceed 4096 bytes + int pitch = bit16 ? ALIGNUP8(bmp->width * 2) : ALIGNUP8(bmp->width * 4); + if (pitch * bmp->height > 4096) return 0; + + CCTexture* tex = Mem_Alloc(1, sizeof(CCTexture), "texture"); + + glGenTextures(1, &tex->textureID); + glBindTexture(GL_TEXTURE_2D, tex->textureID); + // NOTE: Enabling these fixes textures, but seems to break on cen64 + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmaps ? GL_LINEAR : GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mipmaps ? GL_LINEAR : GL_NEAREST); + + tex->surface = surface_alloc(bit16 ? FMT_RGBA16 : FMT_RGBA32, bmp->width, bmp->height); + surface_t* fb = &tex->surface; + + if (bit16) { + cc_uint32* src = (cc_uint32*)bmp->scan0; + cc_uint8* dst = (cc_uint8*)fb->buffer; + + // 16 bpp requires reducing A8R8G8B8 to A1R5G5B5 + for (int y = 0; y < bmp->height; y++) + { + cc_uint32* src_row = src + y * rowWidth; + cc_uint16* dst_row = (cc_uint16*)(dst + y * fb->stride); + + for (int x = 0; x < bmp->width; x++) + { + dst_row[x] = To16BitPixel(src_row[x]); + } + } + } else { + // 32 bpp can just be copied straight across + CopyTextureData(fb->buffer, fb->stride, bmp, rowWidth << 2); + } + + + rdpq_texparms_t params = + { + .s.repeats = (flags & TEXTURE_FLAG_NONPOW2) ? 1 : REPEAT_INFINITE, + .t.repeats = (flags & TEXTURE_FLAG_NONPOW2) ? 1 : REPEAT_INFINITE, + }; + + // rdpq_tex_upload(TILE0, &tex->surface, ¶ms); + glSurfaceTexImageN64(GL_TEXTURE_2D, 0, fb, ¶ms); + return tex; +} + +void Gfx_BindTexture(GfxResourceID texId) { + CCTexture* tex = (CCTexture*)texId; + GLuint glID = tex ? tex->textureID : 0; + //Platform_Log1("BIND: %i", &glID); + + //rdpq_debug_log(true); + glBindTexture(GL_TEXTURE_2D, glID); + // rdpq_debug_log(false); +} + +void Gfx_UpdateTexture(GfxResourceID texId, int x, int y, struct Bitmap* part, int rowWidth, cc_bool mipmaps) { + // TODO: Just memcpying doesn't actually work. maybe due to glSurfaceTexImageN64 caching the RSQ upload block? + // TODO: Is there a more optimised approach than just calling glSurfaceTexImageN64 + CCTexture* tex = (CCTexture*)texId; + + surface_t* fb = &tex->surface; + cc_uint32* src = (cc_uint32*)part->scan0 + x; + cc_uint8* dst = (cc_uint8*)fb->buffer + (x * 4) + (y * fb->stride); + + for (int srcY = 0; srcY < part->height; srcY++) + { + Mem_Copy(dst + srcY * fb->stride, + src + srcY * rowWidth, + part->width * 4); + } + + + glBindTexture(GL_TEXTURE_2D, tex->textureID); + rdpq_texparms_t params = (rdpq_texparms_t){ + .s.repeats = REPEAT_INFINITE, + .t.repeats = REPEAT_INFINITE, + }; + // rdpq_tex_upload(TILE0, &tex->surface, ¶ms); + glSurfaceTexImageN64(GL_TEXTURE_2D, 0, fb, ¶ms); +} + +void Gfx_DeleteTexture(GfxResourceID* texId) { + CCTexture* tex = (CCTexture*)(*texId); + if (!tex) return; + + glDeleteTextures(1, &tex->textureID); + Mem_Free(tex); + *texId = NULL; +} + +void Gfx_EnableMipmaps(void) { } +void Gfx_DisableMipmaps(void) { } + + +/*########################################################################################################################* +*-----------------------------------------------------State management----------------------------------------------------* +*#########################################################################################################################*/ +void Gfx_SetFaceCulling(cc_bool enabled) { gl_Toggle(GL_CULL_FACE); } +static void SetAlphaBlend(cc_bool enabled) { gl_Toggle(GL_BLEND); } +void Gfx_SetAlphaArgBlend(cc_bool enabled) { } + +static void SetColorWrite(cc_bool r, cc_bool g, cc_bool b, cc_bool a) { + //glColorMask(r, g, b, a); TODO +} + +void Gfx_SetDepthWrite(cc_bool enabled) { glDepthMask(enabled); } +void Gfx_SetDepthTest(cc_bool enabled) { gl_Toggle(GL_DEPTH_TEST); } + +static void Gfx_FreeState(void) { FreeDefaultResources(); } +static void Gfx_RestoreState(void) { + InitDefaultResources(); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + gfx_format = -1; + + glHint(GL_FOG_HINT, GL_NICEST); + glAlphaFunc(GL_GREATER, 0.5f); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthFunc(GL_LESS); + //glEnable(GL_RDPQ_TEXTURING_N64); +} + +cc_bool Gfx_WarnIfNecessary(void) { return false; } + + +/*########################################################################################################################* +*---------------------------------------------------------Matrices--------------------------------------------------------* +*#########################################################################################################################*/ +void Gfx_CalcOrthoMatrix(struct Matrix* matrix, float width, float height, float zNear, float zFar) { + /* Transposed, source https://learn.microsoft.com/en-us/windows/win32/opengl/glortho */ + /* 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 = -2.0f / (zFar - zNear); + + matrix->row4.x = -1.0f; + matrix->row4.y = 1.0f; + matrix->row4.z = -(zFar + zNear) / (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 https://learn.microsoft.com/en-us/windows/win32/opengl/glfrustum */ + /* For a FOV based perspective matrix, left/right/top/bottom are calculated as: */ + /* left = -c * aspect, right = c * aspect, bottom = -c, top = c */ + /* Calculations are simplified because of left/right and top/bottom symmetry */ + *matrix = Matrix_Identity; + + matrix->row1.x = c / aspect; + matrix->row2.y = c; + matrix->row3.z = -(zFar + zNear) / (zFar - zNear); + matrix->row3.w = -1.0f; + matrix->row4.z = -(2.0f * zFar * zNear) / (zFar - zNear); + matrix->row4.w = 0.0f; +} + + +/*########################################################################################################################* +*----------------------------------------------------------Buffers--------------------------------------------------------* +*#########################################################################################################################*/ +static cc_uint16 __attribute__((aligned(16))) gfx_indices[GFX_MAX_INDICES]; +static void* gfx_vertices; +static int vb_size; + +GfxResourceID Gfx_CreateIb2(int count, Gfx_FillIBFunc fillFunc, void* obj) { + fillFunc(gfx_indices, count, obj); + return (void*)1; +} + +void Gfx_BindIb(GfxResourceID ib) { } +void Gfx_DeleteIb(GfxResourceID* ib) { } + + +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) { + vb_size = count * strideSizes[fmt]; + 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) { + vb_size = count * strideSizes[fmt]; + return vb; +} + +void Gfx_UnlockDynamicVb(GfxResourceID vb) { + gfx_vertices = vb; +} + +void Gfx_DeleteDynamicVb(GfxResourceID* vb) { Gfx_DeleteVb(vb); } + + +/*########################################################################################################################* +*-----------------------------------------------------State management----------------------------------------------------* +*#########################################################################################################################*/ +static cc_bool depthOnlyRendering; + +void Gfx_SetFog(cc_bool enabled) { +} + +void Gfx_SetFogCol(PackedCol color) { +} + +void Gfx_SetFogDensity(float value) { +} + +void Gfx_SetFogEnd(float value) { +} + +void Gfx_SetFogMode(FogFunc func) { +} + +static void SetAlphaTest(cc_bool enabled) { + if (enabled) { glEnable(GL_ALPHA_TEST); } else { glDisable(GL_ALPHA_TEST); } +} + +void Gfx_DepthOnlyRendering(cc_bool depthOnly) { + depthOnlyRendering = depthOnly; // TODO: Better approach? maybe using glBlendFunc instead? + cc_bool enabled = !depthOnly; + //SetColorWrite(enabled & gfx_colorMask[0], enabled & gfx_colorMask[1], + // enabled & gfx_colorMask[2], enabled & gfx_colorMask[3]); + if (enabled) { glEnable(GL_TEXTURE_2D); } else { glDisable(GL_TEXTURE_2D); } +} + + +/*########################################################################################################################* +*---------------------------------------------------------Matrices--------------------------------------------------------* +*#########################################################################################################################*/ +static GLenum matrix_modes[3] = { GL_PROJECTION, GL_MODELVIEW, GL_TEXTURE }; +static int lastMatrix; + +void Gfx_LoadMatrix(MatrixType type, const struct Matrix* matrix) { + if (type != lastMatrix) { lastMatrix = type; glMatrixMode(matrix_modes[type]); } + glLoadMatrixf((const float*)matrix); +} + +void Gfx_LoadIdentityMatrix(MatrixType type) { + if (type != lastMatrix) { lastMatrix = type; glMatrixMode(matrix_modes[type]); } + glLoadIdentity(); +} + +static struct Matrix texMatrix = Matrix_IdentityValue; +void Gfx_EnableTextureOffset(float x, float y) { + texMatrix.row4.x = x; texMatrix.row4.y = y; + Gfx_LoadMatrix(2, &texMatrix); +} + +void Gfx_DisableTextureOffset(void) { Gfx_LoadIdentityMatrix(2); } + + +/*########################################################################################################################* +*--------------------------------------------------------Rendering--------------------------------------------------------* +*#########################################################################################################################*/ +#define VB_PTR gfx_vertices +#define IB_PTR gfx_indices + +static void GL_SetupVbColoured(void) { + glVertexPointer(3, GL_FLOAT, SIZEOF_VERTEX_COLOURED, (void*)(VB_PTR + 0)); + glColorPointer(4, GL_UNSIGNED_BYTE, SIZEOF_VERTEX_COLOURED, (void*)(VB_PTR + 12)); +} + +static void GL_SetupVbTextured(void) { + glVertexPointer(3, GL_FLOAT, SIZEOF_VERTEX_TEXTURED, (void*)(VB_PTR + 0)); + glColorPointer(4, GL_UNSIGNED_BYTE, SIZEOF_VERTEX_TEXTURED, (void*)(VB_PTR + 12)); + glTexCoordPointer(2, GL_FLOAT, SIZEOF_VERTEX_TEXTURED, (void*)(VB_PTR + 16)); +} + +static void GL_SetupVbColoured_Range(int startVertex) { + cc_uint32 offset = startVertex * SIZEOF_VERTEX_COLOURED; + glVertexPointer(3, GL_FLOAT, SIZEOF_VERTEX_COLOURED, (void*)(VB_PTR + offset)); + glColorPointer(4, GL_UNSIGNED_BYTE, SIZEOF_VERTEX_COLOURED, (void*)(VB_PTR + offset + 12)); +} + +static void GL_SetupVbTextured_Range(int startVertex) { + cc_uint32 offset = startVertex * SIZEOF_VERTEX_TEXTURED; + glVertexPointer(3, GL_FLOAT, SIZEOF_VERTEX_TEXTURED, (void*)(VB_PTR + offset)); + glColorPointer(4, GL_UNSIGNED_BYTE, SIZEOF_VERTEX_TEXTURED, (void*)(VB_PTR + offset + 12)); + glTexCoordPointer(2, GL_FLOAT, SIZEOF_VERTEX_TEXTURED, (void*)(VB_PTR + offset + 16)); +} + +void Gfx_SetVertexFormat(VertexFormat fmt) { + if (fmt == gfx_format) return; + gfx_format = fmt; + gfx_stride = strideSizes[fmt]; + + if (fmt == VERTEX_FORMAT_TEXTURED) { + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnable(GL_TEXTURE_2D); + + gfx_setupVBFunc = GL_SetupVbTextured; + gfx_setupVBRangeFunc = GL_SetupVbTextured_Range; + } else { + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisable(GL_TEXTURE_2D); + + gfx_setupVBFunc = GL_SetupVbColoured; + gfx_setupVBRangeFunc = GL_SetupVbColoured_Range; + } +} + +void Gfx_DrawVb_Lines(int verticesCount) { + gfx_setupVBFunc(); + glDrawArrays(GL_LINES, 0, verticesCount); +} + +void Gfx_DrawVb_IndexedTris_Range(int verticesCount, int startVertex) { + gfx_setupVBRangeFunc(startVertex); + glDrawElements(GL_TRIANGLES, ICOUNT(verticesCount), GL_UNSIGNED_SHORT, IB_PTR); +} + +void Gfx_DrawVb_IndexedTris(int verticesCount) { + gfx_setupVBFunc(); + glDrawElements(GL_TRIANGLES, ICOUNT(verticesCount), GL_UNSIGNED_SHORT, IB_PTR); +} + +void Gfx_DrawIndexedTris_T2fC4b(int verticesCount, int startVertex) { + cc_uint32 offset = startVertex * SIZEOF_VERTEX_TEXTURED; + if (depthOnlyRendering) return; + glVertexPointer(3, GL_FLOAT, SIZEOF_VERTEX_TEXTURED, (void*)(VB_PTR + offset)); + glColorPointer(4, GL_UNSIGNED_BYTE, SIZEOF_VERTEX_TEXTURED, (void*)(VB_PTR + offset + 12)); + glTexCoordPointer(2, GL_FLOAT, SIZEOF_VERTEX_TEXTURED, (void*)(VB_PTR + offset + 16)); + glDrawElements(GL_TRIANGLES, ICOUNT(verticesCount), GL_UNSIGNED_SHORT, IB_PTR); +} +#endif |