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_Dreamcast.c |
initial commit
Diffstat (limited to 'src/Graphics_Dreamcast.c')
-rw-r--r-- | src/Graphics_Dreamcast.c | 643 |
1 files changed, 643 insertions, 0 deletions
diff --git a/src/Graphics_Dreamcast.c b/src/Graphics_Dreamcast.c new file mode 100644 index 0000000..8da6c8c --- /dev/null +++ b/src/Graphics_Dreamcast.c @@ -0,0 +1,643 @@ +#include "Core.h" +#if defined CC_BUILD_DREAMCAST +#include "_GraphicsBase.h" +#include "Errors.h" +#include "Logger.h" +#include "Window.h" +#include "../third_party/gldc/src/sh4.h" +#include <malloc.h> +#include <kos.h> +#include <dc/matrix.h> +#include <dc/pvr.h> + +static cc_bool renderingDisabled; +#define VERTEX_BUFFER_SIZE 32 * 40000 + + +/*########################################################################################################################* +*---------------------------------------------------------General---------------------------------------------------------* +*#########################################################################################################################*/ +static int InitPowerVR(void) { + cc_bool autosort = false; // Turn off auto sorting to match traditional GPU behaviour + cc_bool fsaa = false; + AUTOSORT_ENABLED = autosort; + + pvr_init_params_t params = { + // Opaque, punch through, translucent polygons with largest bin sizes + { PVR_BINSIZE_32, PVR_BINSIZE_0, PVR_BINSIZE_32, PVR_BINSIZE_0, PVR_BINSIZE_32 }, + VERTEX_BUFFER_SIZE, + 0, fsaa, + (autosort) ? 0 : 1 + }; + pvr_init(¶ms); +} + +static void InitGLState(void) { + glClearDepth(1.0f); + GPUSetAlphaCutOff(127); + + ALPHA_TEST_ENABLED = GL_FALSE; + CULLING_ENABLED = GL_FALSE; + BLEND_ENABLED = GL_FALSE; + DEPTH_TEST_ENABLED = GL_FALSE; + DEPTH_MASK_ENABLED = GL_TRUE; + TEXTURES_ENABLED = GL_FALSE; + FOG_ENABLED = GL_FALSE; + + STATE_DIRTY = GL_TRUE; +} + +void Gfx_Create(void) { + if (!Gfx.Created) InitPowerVR(); + if (!Gfx.Created) glKosInit(); + + Gfx_SetViewport(0, 0, Game.Width, Game.Height); + InitGLState(); + + Gfx.MinTexWidth = 8; + Gfx.MinTexHeight = 8; + Gfx.MaxTexWidth = 1024; + Gfx.MaxTexHeight = 1024; + Gfx.MaxTexSize = 512 * 512; // reasonable cap as Dreamcast only has 8MB VRAM + Gfx.Created = true; + + Gfx_RestoreState(); +} + +cc_bool Gfx_TryRestoreContext(void) { + return true; +} + +void Gfx_Free(void) { + Gfx_FreeState(); +} + + +/*########################################################################################################################* +*-----------------------------------------------------State management----------------------------------------------------* +*#########################################################################################################################*/ +static PackedCol gfx_clearColor; + +void Gfx_SetFaceCulling(cc_bool enabled) { + CULLING_ENABLED = enabled; + STATE_DIRTY = GL_TRUE; +} + +static void SetAlphaBlend(cc_bool enabled) { + BLEND_ENABLED = enabled; + STATE_DIRTY = GL_TRUE; +} +void Gfx_SetAlphaArgBlend(cc_bool enabled) { } + +void Gfx_ClearColor(PackedCol color) { + if (color == gfx_clearColor) return; + gfx_clearColor = color; + + float r = PackedCol_R(color) / 255.0f; + float g = PackedCol_G(color) / 255.0f; + float b = PackedCol_B(color) / 255.0f; + pvr_set_bg_color(r, g, b); // TODO: not working ? +} + +static void SetColorWrite(cc_bool r, cc_bool g, cc_bool b, cc_bool a) { + // TODO: Doesn't work +} + +void Gfx_SetDepthWrite(cc_bool enabled) { + if (DEPTH_MASK_ENABLED == enabled) return; + + DEPTH_MASK_ENABLED = enabled; + STATE_DIRTY = GL_TRUE; +} + +void Gfx_SetDepthTest(cc_bool enabled) { + if (DEPTH_TEST_ENABLED == enabled) return; + + DEPTH_TEST_ENABLED = enabled; + STATE_DIRTY = GL_TRUE; +} + +static void SetAlphaTest(cc_bool enabled) { + ALPHA_TEST_ENABLED = enabled; + STATE_DIRTY = GL_TRUE; +} + +void Gfx_DepthOnlyRendering(cc_bool depthOnly) { + // don't need a fake second pass in this case + renderingDisabled = depthOnly; +} + + +/*########################################################################################################################* +*---------------------------------------------------------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; +} + + +/*########################################################################################################################* +*-------------------------------------------------------Index buffers-----------------------------------------------------* +*#########################################################################################################################*/ +static void* gfx_vertices; +static int vb_size; + +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 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; + //sceKernelDcacheWritebackInvalidateRange(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; + //dcache_flush_range(vb, vb_size); +} + +void Gfx_DeleteDynamicVb(GfxResourceID* vb) { Gfx_DeleteVb(vb); } + + +/*########################################################################################################################* +*---------------------------------------------------------Textures--------------------------------------------------------* +*#########################################################################################################################*/ +void Gfx_BindTexture(GfxResourceID texId) { + gldcBindTexture((GLuint)texId); +} + +void Gfx_DeleteTexture(GfxResourceID* texId) { + GLuint id = (GLuint)(*texId); + if (!id) return; + gldcDeleteTexture(id); + *texId = 0; +} + +void Gfx_EnableMipmaps(void) { } +void Gfx_DisableMipmaps(void) { } + + +static unsigned Interleave(unsigned x) { + // Simplified "Interleave bits by Binary Magic Numbers" from + // http://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN + + x = (x | (x << 8)) & 0x00FF00FF; + x = (x | (x << 4)) & 0x0F0F0F0F; + x = (x | (x << 2)) & 0x33333333; + x = (x | (x << 1)) & 0x55555555; + return x; +} + +/*static int CalcTwiddledIndex(int x, int y, int w, int h) { + // Twiddled index looks like this (lowest numbered bits are leftmost): + // - w = h: yxyx yxyx + // - w > h: yxyx xxxx + // - h > w: yxyx yyyy + // And can therefore be broken down into two components: + // 1) interleaved lower bits + // 2) masked and then shifted higher bits + + int min_dimension = Math.Min(w, h); + + int interleave_mask = min_dimension - 1; + int interleaved_bits = Math_ilog2(min_dimension); + + int shifted_mask = (~0) & ~interleave_mask; + // as lower interleaved bits contain both X and Y, need to adjust the + // higher bit shift by number of interleaved bits used by other axis + int shift_bits = interleaved_bits; + + // For example, in the case of W=4 and H=8 + // the bit pattern is yx_yx_yx_Y + // - lower 3 Y bits are interleaved + // - upper 1 Y bit must be shifted right 3 bits + + int lo_Y = Interleave(y & interleave_mask); + int hi_Y = (y & shifted_mask) << shift_bits; + int Y = lo_Y | hi_Y; + + int lo_X = Interleave(x & interleave_mask) << 1; + int hi_X = (x & shifted_mask) << shift_bits; + int X = lo_X | hi_X; + + return X | Y; +}*/ + +#define Twiddle_CalcFactors(w, h) \ + min_dimension = min(w, h); \ + interleave_mask = min_dimension - 1; \ + interleaved_bits = Math_ilog2(min_dimension); \ + shifted_mask = ~interleave_mask; \ + shift_bits = interleaved_bits; + +#define Twiddle_CalcY(y) \ + lo_Y = Interleave(y & interleave_mask); \ + hi_Y = (y & shifted_mask) << shift_bits; \ + Y = lo_Y | hi_Y; + +#define Twiddle_CalcX(x) \ + lo_X = Interleave(x & interleave_mask) << 1; \ + hi_X = (x & shifted_mask) << shift_bits; \ + X = lo_X | hi_X; + + +// B8 G8 R8 A8 > B4 G4 R4 A4 +#define BGRA8_to_BGRA4(src) \ + ((src[0] & 0xF0) >> 4) | (src[1] & 0xF0) | ((src[2] & 0xF0) << 4) | ((src[3] & 0xF0) << 8); + +static void ConvertTexture(cc_uint16* dst, struct Bitmap* bmp, int rowWidth) { + unsigned min_dimension; + unsigned interleave_mask, interleaved_bits; + unsigned shifted_mask, shift_bits; + unsigned lo_Y, hi_Y, Y; + unsigned lo_X, hi_X, X; + Twiddle_CalcFactors(bmp->width, bmp->height); + + for (int y = 0; y < bmp->height; y++) + { + Twiddle_CalcY(y); + cc_uint8* src = (cc_uint8*)(bmp->scan0 + y * rowWidth); + + for (int x = 0; x < bmp->width; x++, src += 4) + { + Twiddle_CalcX(x); + dst[X | Y] = BGRA8_to_BGRA4(src); + } + } +} + +static GfxResourceID Gfx_AllocTexture(struct Bitmap* bmp, int rowWidth, cc_uint8 flags, cc_bool mipmaps) { + GLuint texId = gldcGenTexture(); + gldcBindTexture(texId); + + int res = gldcAllocTexture(bmp->width, bmp->height, PVR_TXRFMT_ARGB4444); + if (res) { Platform_LogConst("Out of PVR VRAM!"); return 0; } + + void* pixels; + int width, height; + gldcGetTexture(&pixels, &width, &height); + ConvertTexture(pixels, bmp, rowWidth); + return texId; +} + +// TODO: struct GPUTexture ?? +static void ConvertSubTexture(cc_uint16* dst, int texWidth, int texHeight, + int originX, int originY, + struct Bitmap* bmp, int rowWidth) { + unsigned min_dimension; + unsigned interleave_mask, interleaved_bits; + unsigned shifted_mask, shift_bits; + unsigned lo_Y, hi_Y, Y; + unsigned lo_X, hi_X, X; + Twiddle_CalcFactors(texWidth, texHeight); + + for (int y = 0; y < bmp->height; y++) + { + int dstY = y + originY; + Twiddle_CalcY(dstY); + cc_uint8* src = (cc_uint8*)(bmp->scan0 + rowWidth * y); + + for (int x = 0; x < bmp->width; x++, src += 4) + { + int dstX = x + originX; + Twiddle_CalcX(dstX); + dst[X | Y] = BGRA8_to_BGRA4(src); + } + } +} + +void Gfx_UpdateTexture(GfxResourceID texId, int x, int y, struct Bitmap* part, int rowWidth, cc_bool mipmaps) { + gldcBindTexture(texId); + + void* pixels; + int width, height; + gldcGetTexture(&pixels, &width, &height); + + ConvertSubTexture(pixels, width, height, + x, y, part, rowWidth); + // TODO: Do we need to flush VRAM? +} + + +/*########################################################################################################################* +*-----------------------------------------------------State management----------------------------------------------------* +*#########################################################################################################################*/ +static PackedCol gfx_fogColor; +static float gfx_fogEnd = 16.0f, gfx_fogDensity = 1.0f; +static FogFunc gfx_fogMode = -1; + +void Gfx_SetFog(cc_bool enabled) { + gfx_fogEnabled = enabled; + if (FOG_ENABLED == enabled) return; + + FOG_ENABLED = enabled; + STATE_DIRTY = GL_TRUE; +} + +void Gfx_SetFogCol(PackedCol color) { + if (color == gfx_fogColor) return; + gfx_fogColor = color; + + float r = PackedCol_R(color) / 255.0f; + float g = PackedCol_G(color) / 255.0f; + float b = PackedCol_B(color) / 255.0f; + float a = PackedCol_A(color) / 255.0f; + + pvr_fog_table_color(a, r, g, b); +} + +static void UpdateFog(void) { + if (gfx_fogMode == FOG_LINEAR) { + pvr_fog_table_linear(0.0f, gfx_fogEnd); + } else if (gfx_fogMode == FOG_EXP) { + pvr_fog_table_exp(gfx_fogDensity); + } else if (gfx_fogMode == FOG_EXP2) { + pvr_fog_table_exp2(gfx_fogDensity); + } +} + +void Gfx_SetFogDensity(float value) { + if (value == gfx_fogDensity) return; + gfx_fogDensity = value; + UpdateFog(); +} + +void Gfx_SetFogEnd(float value) { + if (value == gfx_fogEnd) return; + gfx_fogEnd = value; + UpdateFog(); +} + +void Gfx_SetFogMode(FogFunc func) { + if (func == gfx_fogMode) return; + gfx_fogMode = func; + UpdateFog(); +} + + +/*########################################################################################################################* +*---------------------------------------------------------Matrices--------------------------------------------------------* +*#########################################################################################################################*/ +static matrix_t __attribute__((aligned(32))) _proj, _view; +static float textureOffsetX, textureOffsetY; +static int textureOffset; + +void Gfx_LoadMatrix(MatrixType type, const struct Matrix* matrix) { + if (type == MATRIX_PROJECTION) memcpy(&_proj, matrix, sizeof(struct Matrix)); + if (type == MATRIX_VIEW) memcpy(&_view, matrix, sizeof(struct Matrix)); + + mat_load( &_proj); + mat_apply(&_view); +} + +void Gfx_LoadIdentityMatrix(MatrixType type) { + Gfx_LoadMatrix(type, &Matrix_Identity); +} + + +void Gfx_EnableTextureOffset(float x, float y) { + textureOffset = true; + textureOffsetX = x; + textureOffsetY = y; +} + +void Gfx_DisableTextureOffset(void) { + textureOffset = false; +} + +static CC_NOINLINE void ShiftTextureCoords(int count) { + for (int i = 0; i < count; i++) + { + struct VertexTextured* v = (struct VertexTextured*)gfx_vertices + i; + v->U += textureOffsetX; + v->V += textureOffsetY; + } +} + +static CC_NOINLINE void UnshiftTextureCoords(int count) { + for (int i = 0; i < count; i++) + { + struct VertexTextured* v = (struct VertexTextured*)gfx_vertices + i; + v->U -= textureOffsetX; + v->V -= textureOffsetY; + } +} + + +/*########################################################################################################################* +*-------------------------------------------------------State setup-------------------------------------------------------* +*#########################################################################################################################*/ +static void Gfx_FreeState(void) { FreeDefaultResources(); } +static void Gfx_RestoreState(void) { + InitDefaultResources(); + gfx_format = -1; +} + +cc_bool Gfx_WarnIfNecessary(void) { + return false; +} + + +/*########################################################################################################################* +*----------------------------------------------------------Drawing--------------------------------------------------------* +*#########################################################################################################################*/ +extern void apply_poly_header(PolyHeader* header, PolyList* activePolyList); + +extern Vertex* DrawColouredQuads(const void* src, Vertex* dst, int numQuads); +extern Vertex* DrawTexturedQuads(const void* src, Vertex* dst, int numQuads); + +void DrawQuads(int count, void* src) { + if (!count) return; + PolyList* output = _glActivePolyList(); + AlignedVectorHeader* hdr = &output->vector.hdr; + + uint32_t header_required = (hdr->size == 0) || STATE_DIRTY; + // Reserve room for the vertices and header + Vertex* beg = aligned_vector_reserve(&output->vector, hdr->size + (header_required) + count); + + if (header_required) { + apply_poly_header((PolyHeader*)beg, output); + STATE_DIRTY = GL_FALSE; + beg++; + hdr->size += 1; + } + Vertex* end; + + if (TEXTURES_ENABLED) { + end = DrawTexturedQuads(src, beg, count >> 2); + } else { + end = DrawColouredQuads(src, beg, count >> 2); + } + hdr->size += (end - beg); +} + +void Gfx_SetVertexFormat(VertexFormat fmt) { + if (fmt == gfx_format) return; + gfx_format = fmt; + gfx_stride = strideSizes[fmt]; + + TEXTURES_ENABLED = fmt == VERTEX_FORMAT_TEXTURED; + STATE_DIRTY = GL_TRUE; +} + +void Gfx_DrawVb_Lines(int verticesCount) { + //SetupVertices(0); + //glDrawArrays(GL_LINES, 0, verticesCount); +} + +void Gfx_DrawVb_IndexedTris_Range(int verticesCount, int startVertex) { + void* src; + if (gfx_format == VERTEX_FORMAT_TEXTURED) { + src = gfx_vertices + startVertex * SIZEOF_VERTEX_TEXTURED; + } else { + src = gfx_vertices + startVertex * SIZEOF_VERTEX_COLOURED; + } + + DrawQuads(verticesCount, src); +} + +void Gfx_DrawVb_IndexedTris(int verticesCount) { + if (textureOffset) ShiftTextureCoords(verticesCount); + DrawQuads(verticesCount, gfx_vertices); + if (textureOffset) UnshiftTextureCoords(verticesCount); +} + +void Gfx_DrawIndexedTris_T2fC4b(int verticesCount, int startVertex) { + if (renderingDisabled) return; + + void* src = gfx_vertices + startVertex * SIZEOF_VERTEX_TEXTURED; + DrawQuads(verticesCount, src); +} + + +/*########################################################################################################################* +*-----------------------------------------------------------Misc----------------------------------------------------------* +*#########################################################################################################################*/ +cc_result Gfx_TakeScreenshot(struct Stream* output) { + return ERR_NOT_SUPPORTED; +} + +void Gfx_GetApiInfo(cc_string* info) { + int freeMem = _glFreeTextureMemory(); + int usedMem = _glUsedTextureMemory(); + + float freeMemMB = freeMem / (1024.0 * 1024.0); + float usedMemMB = usedMem / (1024.0 * 1024.0); + + String_AppendConst(info, "-- Using Dreamcast --\n"); + String_AppendConst(info, "GPU: PowerVR2 CLX2 100mHz\n"); + String_AppendConst(info, "T&L: GLdc library (KallistiOS / Kazade)\n"); + String_Format2(info, "Texture memory: %f2 MB used, %f2 MB free\n", &usedMemMB, &freeMemMB); + 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 + // no need to use glClear +} + +void Gfx_EndFrame(void) { + pvr_wait_ready(); + glKosSwapBuffers(); + if (gfx_minFrameMs) LimitFPS(); +} + +void Gfx_OnWindowResize(void) { + Gfx_SetViewport(0, 0, Game.Width, Game.Height); +} + +extern float VP_COL_HWIDTH, VP_TEX_HWIDTH; +extern float VP_COL_HHEIGHT, VP_TEX_HHEIGHT; +extern float VP_COL_X_PLUS_HWIDTH, VP_TEX_X_PLUS_HWIDTH; +extern float VP_COL_Y_PLUS_HHEIGHT, VP_TEX_Y_PLUS_HHEIGHT; + +void Gfx_SetViewport(int x, int y, int w, int h) { + if (x == 0 && y == 0 && w == Game.Width && h == Game.Height) { + SCISSOR_TEST_ENABLED = GL_FALSE; + } else { + SCISSOR_TEST_ENABLED = GL_TRUE; + } + STATE_DIRTY = GL_TRUE; + + glViewport(x, y, w, h); + glScissor (x, y, w, h); + + VP_COL_HWIDTH = VP_TEX_HWIDTH = w * 0.5f; + VP_COL_HHEIGHT = VP_TEX_HHEIGHT = h * -0.5f; + + VP_COL_X_PLUS_HWIDTH = VP_TEX_X_PLUS_HWIDTH = x + w * 0.5f; + VP_COL_Y_PLUS_HHEIGHT = VP_TEX_Y_PLUS_HHEIGHT = y + h * 0.5f; +} +#endif |