ClassiCube/src/_GraphicsBase.h

521 lines
19 KiB
C

#include "Graphics.h"
#include "String.h"
#include "Platform.h"
#include "Funcs.h"
#include "Game.h"
#include "ExtMath.h"
#include "Event.h"
#include "Block.h"
#include "Options.h"
#include "Bitmap.h"
#include "Chat.h"
#include "Logger.h"
struct _GfxData Gfx;
static GfxResourceID Gfx_quadVb, Gfx_texVb;
const cc_string Gfx_LowPerfMessage = String_FromConst("&eRunning in reduced performance mode (game minimised or hidden)");
static const int strideSizes[] = { SIZEOF_VERTEX_COLOURED, SIZEOF_VERTEX_TEXTURED };
/* Whether mipmaps must be created for all dimensions down to 1x1 or not */
static cc_bool customMipmapsLevels;
/* Current format and size of vertices */
static int gfx_stride, gfx_format = -1;
static cc_bool gfx_vsync, gfx_fogEnabled;
static cc_bool gfx_rendering2D;
/*########################################################################################################################*
*------------------------------------------------------State changes------------------------------------------------------*
*#########################################################################################################################*/
static cc_bool gfx_colorMask[4] = { true, true, true, true };
cc_bool Gfx_GetFog(void) { return gfx_fogEnabled; }
static cc_bool gfx_alphaTest, gfx_alphaBlend;
static void SetAlphaTest(cc_bool enabled);
void Gfx_SetAlphaTest(cc_bool enabled) {
if (gfx_alphaTest == enabled) return;
gfx_alphaTest = enabled;
SetAlphaTest(enabled);
}
static void SetAlphaBlend(cc_bool enabled);
void Gfx_SetAlphaBlending(cc_bool enabled) {
if (gfx_alphaBlend == enabled) return;
gfx_alphaBlend = enabled;
SetAlphaBlend(enabled);
}
/* Initialises/Restores render state */
CC_NOINLINE static void Gfx_RestoreState(void);
/* Destroys render state, but can be restored later */
CC_NOINLINE static void Gfx_FreeState(void);
static void SetColorWrite(cc_bool r, cc_bool g, cc_bool b, cc_bool a);
void Gfx_SetColorWrite(cc_bool r, cc_bool g, cc_bool b, cc_bool a) {
gfx_colorMask[0] = r;
gfx_colorMask[1] = g;
gfx_colorMask[2] = b;
gfx_colorMask[3] = a;
SetColorWrite(r, g, b, a);
}
void Gfx_SetTexturing(cc_bool enabled) { } /* useless */
#ifndef CC_BUILD_3DS
void Gfx_Set3DLeft(struct Matrix* proj, struct Matrix* view) {
struct Matrix proj_left, view_left;
/* Translation values according to values captured by */
/* analysing the OpenGL calls made by classic using gDEbugger */
/* TODO these still aren't quite right, ghosting occurs */
Matrix_Translate(&proj_left, 0.07f, 0, 0);
Matrix_Mul(&Gfx.Projection, proj, &proj_left);
Matrix_Translate(&view_left, -0.10f, 0, 0);
Matrix_Mul(&Gfx.View, view, &view_left);
Gfx_SetColorWrite(false, true, true, false);
}
void Gfx_Set3DRight(struct Matrix* proj, struct Matrix* view) {
struct Matrix proj_right, view_right;
Matrix_Translate(&proj_right, -0.07f, 0, 0);
Matrix_Mul(&Gfx.Projection, proj, &proj_right);
Matrix_Translate(&view_right, 0.10f, 0, 0);
Matrix_Mul(&Gfx.View, view, &view_right);
Gfx_ClearBuffers(GFX_BUFFER_DEPTH);
Gfx_SetColorWrite(true, false, false, false);
}
void Gfx_End3D(struct Matrix* proj, struct Matrix* view) {
Gfx.Projection = *proj;
Gfx_SetColorWrite(true, true, true, true);
}
#endif
/*########################################################################################################################*
*------------------------------------------------------Generic/Common-----------------------------------------------------*
*#########################################################################################################################*/
/* Fills out indices array with {0,1,2} {2,3,0}, {4,5,6} {6,7,4} etc */
static void MakeIndices(cc_uint16* indices, int count, void* obj) {
int element = 0, i;
for (i = 0; i < count; i += 6) {
indices[0] = (cc_uint16)(element + 0);
indices[1] = (cc_uint16)(element + 1);
indices[2] = (cc_uint16)(element + 2);
indices[3] = (cc_uint16)(element + 2);
indices[4] = (cc_uint16)(element + 3);
indices[5] = (cc_uint16)(element + 0);
indices += 6; element += 4;
}
}
static void RecreateDynamicVb(GfxResourceID* vb, VertexFormat fmt, int maxVertices) {
Gfx_DeleteDynamicVb(vb);
*vb = Gfx_CreateDynamicVb(fmt, maxVertices);
}
static void InitDefaultResources(void) {
Gfx.DefaultIb = Gfx_CreateIb2(GFX_MAX_INDICES, MakeIndices, NULL);
RecreateDynamicVb(&Gfx_quadVb, VERTEX_FORMAT_COLOURED, 4);
RecreateDynamicVb(&Gfx_texVb, VERTEX_FORMAT_TEXTURED, 4);
}
static void FreeDefaultResources(void) {
Gfx_DeleteDynamicVb(&Gfx_quadVb);
Gfx_DeleteDynamicVb(&Gfx_texVb);
Gfx_DeleteIb(&Gfx.DefaultIb);
}
/*########################################################################################################################*
*------------------------------------------------------FPS and context----------------------------------------------------*
*#########################################################################################################################*/
void Gfx_LoseContext(const char* reason) {
if (Gfx.LostContext) return;
Gfx.LostContext = true;
Platform_Log1("Lost graphics context: %c", reason);
Event_RaiseVoid(&GfxEvents.ContextLost);
}
void Gfx_RecreateContext(void) {
Gfx.LostContext = false;
Platform_LogConst("Recreating graphics context");
Event_RaiseVoid(&GfxEvents.ContextRecreated);
}
static CC_INLINE void TickReducedPerformance(void) {
Thread_Sleep(100); /* 10 FPS */
if (Gfx.ReducedPerfMode) return;
Gfx.ReducedPerfMode = true;
Chat_AddOf(&Gfx_LowPerfMessage, MSG_TYPE_EXTRASTATUS_2);
}
static CC_INLINE void EndReducedPerformance(void) {
if (!Gfx.ReducedPerfMode) return;
Gfx.ReducedPerfModeCooldown = 2;
Gfx.ReducedPerfMode = false;
Chat_AddOf(&String_Empty, MSG_TYPE_EXTRASTATUS_2);
}
/*########################################################################################################################*
*--------------------------------------------------------2D drawing-------------------------------------------------------*
*#########################################################################################################################*/
#ifndef CC_BUILD_3DS
void Gfx_Draw2DFlat(int x, int y, int width, int height, PackedCol color) {
struct VertexColoured* v;
Gfx_SetVertexFormat(VERTEX_FORMAT_COLOURED);
v = (struct VertexColoured*)Gfx_LockDynamicVb(Gfx_quadVb, VERTEX_FORMAT_COLOURED, 4);
v->x = (float)x; v->y = (float)y; v->z = 0; v->Col = color; v++;
v->x = (float)(x + width); v->y = (float)y; v->z = 0; v->Col = color; v++;
v->x = (float)(x + width); v->y = (float)(y + height); v->z = 0; v->Col = color; v++;
v->x = (float)x; v->y = (float)(y + height); v->z = 0; v->Col = color; v++;
Gfx_UnlockDynamicVb(Gfx_quadVb);
Gfx_DrawVb_IndexedTris(4);
}
void Gfx_Draw2DGradient(int x, int y, int width, int height, PackedCol top, PackedCol bottom) {
struct VertexColoured* v;
Gfx_SetVertexFormat(VERTEX_FORMAT_COLOURED);
v = (struct VertexColoured*)Gfx_LockDynamicVb(Gfx_quadVb, VERTEX_FORMAT_COLOURED, 4);
v->x = (float)x; v->y = (float)y; v->z = 0; v->Col = top; v++;
v->x = (float)(x + width); v->y = (float)y; v->z = 0; v->Col = top; v++;
v->x = (float)(x + width); v->y = (float)(y + height); v->z = 0; v->Col = bottom; v++;
v->x = (float)x; v->y = (float)(y + height); v->z = 0; v->Col = bottom; v++;
Gfx_UnlockDynamicVb(Gfx_quadVb);
Gfx_DrawVb_IndexedTris(4);
}
void Gfx_Draw2DTexture(const struct Texture* tex, PackedCol color) {
struct VertexTextured* ptr;
Gfx_SetVertexFormat(VERTEX_FORMAT_TEXTURED);
ptr = (struct VertexTextured*)Gfx_LockDynamicVb(Gfx_texVb, VERTEX_FORMAT_TEXTURED, 4);
Gfx_Make2DQuad(tex, color, &ptr);
Gfx_UnlockDynamicVb(Gfx_texVb);
Gfx_DrawVb_IndexedTris(4);
}
#endif
void Gfx_Make2DQuad(const struct Texture* tex, PackedCol color, struct VertexTextured** vertices) {
float x1 = (float)tex->x, x2 = (float)(tex->x + tex->width);
float y1 = (float)tex->y, y2 = (float)(tex->y + tex->height);
struct VertexTextured* v = *vertices;
#if CC_GFX_BACKEND == CC_GFX_BACKEND_D3D9
/* NOTE: see "https://msdn.microsoft.com/en-us/library/windows/desktop/bb219690(v=vs.85).aspx", */
/* i.e. the msdn article called "Directly Mapping Texels to Pixels (Direct3D 9)" for why we have to do this. */
x1 -= 0.5f; x2 -= 0.5f;
y1 -= 0.5f; y2 -= 0.5f;
#endif
v->x = x1; v->y = y1; v->z = 0; v->Col = color; v->U = tex->uv.u1; v->V = tex->uv.v1; v++;
v->x = x2; v->y = y1; v->z = 0; v->Col = color; v->U = tex->uv.u2; v->V = tex->uv.v1; v++;
v->x = x2; v->y = y2; v->z = 0; v->Col = color; v->U = tex->uv.u2; v->V = tex->uv.v2; v++;
v->x = x1; v->y = y2; v->z = 0; v->Col = color; v->U = tex->uv.u1; v->V = tex->uv.v2; v++;
*vertices = v;
}
#ifndef CC_BUILD_PS1
static cc_bool gfx_hadFog;
void Gfx_Begin2D(int width, int height) {
struct Matrix ortho;
/* intentionally biased more towards positive Z to reduce 2D clipping issues on the DS */
Gfx_CalcOrthoMatrix(&ortho, (float)width, (float)height, -100.0f, 1000.0f);
Gfx_LoadMatrix(MATRIX_PROJ, &ortho);
Gfx_LoadMatrix(MATRIX_VIEW, &Matrix_Identity);
Gfx_SetDepthTest(false);
Gfx_SetDepthWrite(false);
Gfx_SetAlphaBlending(true);
gfx_hadFog = Gfx_GetFog();
if (gfx_hadFog) Gfx_SetFog(false);
gfx_rendering2D = true;
}
void Gfx_End2D(void) {
Gfx_SetDepthTest(true);
Gfx_SetDepthWrite(true);
Gfx_SetAlphaBlending(false);
if (gfx_hadFog) Gfx_SetFog(true);
gfx_rendering2D = false;
}
#endif
/*########################################################################################################################*
*--------------------------------------------------------Misc/Utils-------------------------------------------------------*
*#########################################################################################################################*/
void Gfx_SetupAlphaState(cc_uint8 draw) {
if (draw == DRAW_TRANSLUCENT) Gfx_SetAlphaBlending(true);
if (draw == DRAW_TRANSPARENT) Gfx_SetAlphaTest(true);
if (draw == DRAW_TRANSPARENT_THICK) Gfx_SetAlphaTest(true);
if (draw == DRAW_SPRITE) Gfx_SetAlphaTest(true);
}
void Gfx_RestoreAlphaState(cc_uint8 draw) {
if (draw == DRAW_TRANSLUCENT) Gfx_SetAlphaBlending(false);
if (draw == DRAW_TRANSPARENT) Gfx_SetAlphaTest(false);
if (draw == DRAW_TRANSPARENT_THICK) Gfx_SetAlphaTest(false);
if (draw == DRAW_SPRITE) Gfx_SetAlphaTest(false);
}
static CC_INLINE float Reversed_CalcZNear(float fov, int depthbufferBits) {
/* With reversed z depth, near Z plane can be much closer (with sufficient depth buffer precision) */
/* This reduces clipping with high FOV without sacrificing depth precision for faraway objects */
/* However for low FOV, don't reduce near Z in order to gain a bit more depth precision */
if (depthbufferBits < 24 || fov <= 70 * MATH_DEG2RAD) return 0.05f;
if (fov <= 100 * MATH_DEG2RAD) return 0.025f;
if (fov <= 150 * MATH_DEG2RAD) return 0.0125f;
return 0.00390625f;
}
static void PrintMaxTextureInfo(cc_string* info) {
if (Gfx.MaxTexSize) {
float maxSize = Gfx.MaxTexSize / (1024.0f * 1024.0f);
String_Format3(info, "Max texture size: (%i, %i), up to %f3 MB\n",
&Gfx.MaxTexWidth, &Gfx.MaxTexHeight, &maxSize);
} else {
String_Format2(info, "Max texture size: (%i, %i)\n",
&Gfx.MaxTexWidth, &Gfx.MaxTexHeight);
}
}
/*########################################################################################################################*
*---------------------------------------------------------Textures--------------------------------------------------------*
*#########################################################################################################################*/
void Gfx_RecreateTexture(GfxResourceID* tex, struct Bitmap* bmp, cc_uint8 flags, cc_bool mipmaps) {
Gfx_DeleteTexture(tex);
*tex = Gfx_CreateTexture(bmp, flags, mipmaps);
}
void Gfx_UpdateTexturePart(GfxResourceID texId, int x, int y, struct Bitmap* part, cc_bool mipmaps) {
Gfx_UpdateTexture(texId, x, y, part, part->width, mipmaps);
}
static CC_INLINE void CopyTextureData(void* dst, int dstStride, const struct Bitmap* src, int srcStride) {
cc_uint8* src_ = (cc_uint8*)src->scan0;
cc_uint8* dst_ = (cc_uint8*)dst;
int y;
if (srcStride == dstStride) {
Mem_Copy(dst_, src_, Bitmap_DataSize(src->width, src->height));
} else {
/* Have to copy scanline by scanline */
for (y = 0; y < src->height; y++) {
Mem_Copy(dst_, src_, src->width * BITMAPCOLOR_SIZE);
src_ += srcStride;
dst_ += dstStride;
}
}
}
/* Quoted from http://www.realtimerendering.com/blog/gpus-prefer-premultiplication/ */
/* The short version: if you want your renderer to properly handle textures with alphas when using */
/* bilinear interpolation or mipmapping, you need to premultiply your PNG color data by their (unassociated) alphas. */
static BitmapCol AverageColor(BitmapCol p1, BitmapCol p2) {
cc_uint32 a1, a2, aSum;
cc_uint32 b1, g1, r1;
cc_uint32 b2, g2, r2;
a1 = BitmapCol_A(p1); a2 = BitmapCol_A(p2);
aSum = (a1 + a2);
aSum = aSum > 0 ? aSum : 1; /* avoid divide by 0 below */
/* Convert RGB to pre-multiplied form */
/* TODO: Don't shift when multiplying/averaging */
r1 = BitmapCol_R(p1) * a1; g1 = BitmapCol_G(p1) * a1; b1 = BitmapCol_B(p1) * a1;
r2 = BitmapCol_R(p2) * a2; g2 = BitmapCol_G(p2) * a2; b2 = BitmapCol_B(p2) * a2;
/* https://stackoverflow.com/a/347376 */
/* We need to convert RGB back from the pre-multiplied average into normal form */
/* ((r1 + r2) / 2) / ((a1 + a2) / 2) */
/* but we just cancel out the / 2 */
return BitmapCol_Make(
(r1 + r2) / aSum,
(g1 + g2) / aSum,
(b1 + b2) / aSum,
aSum >> 1);
}
/* Generates the next mipmaps level bitmap by downsampling from the given bitmap. */
static void GenMipmaps(int width, int height, BitmapCol* dst, BitmapCol* src, int srcWidth) {
int x, y;
/* Downsampling from a 1 pixel wide bitmap requires simpler filtering */
if (srcWidth == 1) {
for (y = 0; y < height; y++) {
/* 1x2 bilinear filter */
dst[0] = AverageColor(*src, *(src + srcWidth));
dst += width;
src += (srcWidth << 1);
}
return;
}
for (y = 0; y < height; y++) {
BitmapCol* src0 = src;
BitmapCol* src1 = src + srcWidth;
for (x = 0; x < width; x++) {
int srcX = (x << 1);
/* 2x2 bilinear filter */
BitmapCol ave0 = AverageColor(src0[srcX], src0[srcX + 1]);
BitmapCol ave1 = AverageColor(src1[srcX], src1[srcX + 1]);
dst[x] = AverageColor(ave0, ave1);
}
src += (srcWidth << 1);
dst += width;
}
}
/* Returns the maximum number of mipmaps levels used for given size. */
static CC_NOINLINE int CalcMipmapsLevels(int width, int height) {
int lvlsWidth = Math_ilog2(width), lvlsHeight = Math_ilog2(height);
if (customMipmapsLevels) {
int lvls = min(lvlsWidth, lvlsHeight);
return min(lvls, 4);
} else {
return max(lvlsWidth, lvlsHeight);
}
}
cc_bool Gfx_CheckTextureSize(int width, int height, cc_uint8 flags) {
int maxSize;
if (width > Gfx.MaxTexWidth) return false;
if (height > Gfx.MaxTexHeight) return false;
if (Gfx.MinTexWidth && width < Gfx.MinTexWidth) return false;
if (Gfx.MinTexHeight && height < Gfx.MinTexHeight) return false;
maxSize = Gfx.MaxTexSize;
// low resolution textures may support higher sizes (e.g. Nintendo 64)
if ((flags & TEXTURE_FLAG_LOWRES) && Gfx.MaxLowResTexSize)
maxSize = Gfx.MaxLowResTexSize;
return maxSize == 0 || (width * height <= maxSize);
}
GfxResourceID Gfx_CreateTexture(struct Bitmap* bmp, cc_uint8 flags, cc_bool mipmaps) {
return Gfx_CreateTexture2(bmp, bmp->width, flags, mipmaps);
}
GfxResourceID Gfx_CreateTexture2(struct Bitmap* bmp, int rowWidth, cc_uint8 flags, cc_bool mipmaps) {
if (Gfx.SupportsNonPowTwoTextures && (flags & TEXTURE_FLAG_NONPOW2)) {
/* Texture is being deliberately created and can be successfully created */
/* with non power of two dimensions. Typically used for UI textures */
} else if (!Math_IsPowOf2(bmp->width) || !Math_IsPowOf2(bmp->height)) {
Process_Abort("Textures must have power of two dimensions");
}
if (Gfx.LostContext) return 0;
if (!Gfx_CheckTextureSize(bmp->width, bmp->height, flags)) return 0;
return Gfx_AllocTexture(bmp, rowWidth, flags, mipmaps);
}
void Texture_Render(const struct Texture* tex) {
Gfx_BindTexture(tex->ID);
Gfx_Draw2DTexture(tex, PACKEDCOL_WHITE);
}
void Texture_RenderShaded(const struct Texture* tex, PackedCol shadeColor) {
Gfx_BindTexture(tex->ID);
Gfx_Draw2DTexture(tex, shadeColor);
}
/*########################################################################################################################*
*------------------------------------------------------Vertex buffers-----------------------------------------------------*
*#########################################################################################################################*/
void* Gfx_RecreateAndLockVb(GfxResourceID* vb, VertexFormat fmt, int count) {
Gfx_DeleteVb(vb);
*vb = Gfx_CreateVb(fmt, count);
return Gfx_LockVb(*vb, fmt, count);
}
static GfxResourceID Gfx_AllocStaticVb( VertexFormat fmt, int count);
static GfxResourceID Gfx_AllocDynamicVb(VertexFormat fmt, int maxVertices);
GfxResourceID Gfx_CreateVb(VertexFormat fmt, int count) {
GfxResourceID vb;
/* if (Gfx.LostContext) return 0; TODO check this ???? probably breaks things */
for (;;)
{
if ((vb = Gfx_AllocStaticVb(fmt, count))) return vb;
if (!Game_ReduceVRAM()) Process_Abort("Out of video memory! (allocating static VB)");
}
}
GfxResourceID Gfx_CreateDynamicVb(VertexFormat fmt, int maxVertices) {
GfxResourceID vb;
if (Gfx.LostContext) return 0;
for (;;)
{
if ((vb = Gfx_AllocDynamicVb(fmt, maxVertices))) return vb;
if (!Game_ReduceVRAM()) Process_Abort("Out of video memory! (allocating dynamic VB)");
}
}
#if CC_GFX_BACKEND_IS_GL() || (CC_GFX_BACKEND == CC_GFX_BACKEND_D3D9)
/* Slightly more efficient implementations are defined in the backends */
#else
void Gfx_SetDynamicVbData(GfxResourceID vb, void* vertices, int vCount) {
void* data = Gfx_LockDynamicVb(vb, gfx_format, vCount);
Mem_Copy(data, vertices, vCount * gfx_stride);
Gfx_UnlockDynamicVb(vb);
}
#endif
/*########################################################################################################################*
*----------------------------------------------------Graphics component---------------------------------------------------*
*#########################################################################################################################*/
static void OnContextLost(void* obj) { Gfx_FreeState(); }
static void OnContextRecreated(void* obj) { Gfx_RestoreState(); }
static void OnInit(void) {
Event_Register_(&GfxEvents.ContextLost, NULL, OnContextLost);
Event_Register_(&GfxEvents.ContextRecreated, NULL, OnContextRecreated);
Gfx.Mipmaps = Options_GetBool(OPT_MIPMAPS, false);
if (Gfx.LostContext) return;
OnContextRecreated(NULL);
}
struct IGameComponent Gfx_Component = {
OnInit /* Init */
/* Can't use OnFree because then Gfx would wrongly be the */
/* first component freed, even though it MUST be the last */
/* Instead, Game.c calls Gfx_Free after first freeing all */
/* the other game components. */
};