Add the OpenGL 4.6 renderer for YQ2

https://github.com/yquake2/ref_gl4
This commit is contained in:
atsb 2023-09-18 21:23:31 +02:00 committed by Denis Pauk
parent 7c4a06320a
commit 637933a23c
20 changed files with 19422 additions and 2 deletions

View file

@ -377,12 +377,12 @@ endif
# ----------
# Phony targets
.PHONY : all client game icon server ref_gl1 ref_gl3 ref_gles3 ref_soft ref_vk
.PHONY : all client game icon server ref_gl1 ref_gl3 ref_gles3 ref_soft ref_vk ref_gl4
# ----------
# Builds everything
all: config client server game ref_gl1 ref_gl3 ref_gles3 ref_soft ref_vk
all: config client server game ref_gl1 ref_gl3 ref_gles3 ref_soft ref_vk ref_gl4
# ----------
@ -694,6 +694,38 @@ build/ref_gles3/%.o: %.c
# ----------
# The OpenGL 4.6 renderer lib
ifeq ($(YQ2_OSTYPE), Windows)
ref_gl4:
@echo "===> Building ref_gl4.dll"
${Q}mkdir -p release
$(MAKE) release/ref_gl4.dll
release/ref_gl4.dll : GLAD_INCLUDE = -Isrc/client/refresh/gl4/glad/include
release/ref_gl4.dll : LDFLAGS += -shared
else # not Windows or Darwin - macOS doesn't support OpenGL 4.6
ref_gl4:
@echo "===> Building ref_gl4.so"
${Q}mkdir -p release
$(MAKE) release/ref_gl4.so
release/ref_gl4.so : GLAD_INCLUDE = -Isrc/client/refresh/gl4/glad/include
release/ref_gl4.so : CFLAGS += -fPIC
release/ref_gl4.so : LDFLAGS += -shared
endif # OS specific ref_gl4 stuff
build/ref_gl4/%.o: %.c
@echo "===> CC $<"
${Q}mkdir -p $(@D)
${Q}$(CC) -c $(CFLAGS) $(SDLCFLAGS) $(INCLUDE) $(GLAD_INCLUDE) -o $@ $<
# ----------
# The soft renderer lib
ifeq ($(YQ2_OSTYPE), Windows)
@ -1024,6 +1056,41 @@ endif
# ----------
REFGL4_OBJS_ := \
src/client/refresh/gl4/gl4_draw.o \
src/client/refresh/gl4/gl4_image.o \
src/client/refresh/gl4/gl4_light.o \
src/client/refresh/gl4/gl4_lightmap.o \
src/client/refresh/gl4/gl4_main.o \
src/client/refresh/gl4/gl4_mesh.o \
src/client/refresh/gl4/gl4_misc.o \
src/client/refresh/gl4/gl4_model.o \
src/client/refresh/gl4/gl4_sdl.o \
src/client/refresh/gl4/gl4_surf.o \
src/client/refresh/gl4/gl4_warp.o \
src/client/refresh/gl4/gl4_shaders.o \
src/client/refresh/files/surf.o \
src/client/refresh/files/models.o \
src/client/refresh/files/pcx.o \
src/client/refresh/files/stb.o \
src/client/refresh/files/wal.o \
src/client/refresh/files/pvs.o \
src/common/shared/shared.o \
src/common/md4.o
REFGL4_OBJS_GLADE_ := \
src/client/refresh/gl4/glad/src/glad.o
ifeq ($(YQ2_OSTYPE), Windows)
REFGL4_OBJS_ += \
src/backends/windows/shared/hunk.o
else # not Windows
REFGL4_OBJS_ += \
src/backends/unix/shared/hunk.o
endif
# ----------
REFSOFT_OBJS_ := \
src/client/refresh/soft/sw_aclip.o \
src/client/refresh/soft/sw_alias.o \
@ -1061,6 +1128,7 @@ REFSOFT_OBJS_ += \
endif
# ----------
REFVK_OBJS_ := \
src/client/refresh/vk/vk_buffer.o \
src/client/refresh/vk/vk_cmd.o \
@ -1160,6 +1228,8 @@ REFGL3_OBJS = $(patsubst %,build/ref_gl3/%,$(REFGL3_OBJS_))
REFGL3_OBJS += $(patsubst %,build/ref_gl3/%,$(REFGL3_OBJS_GLADE_))
REFGLES3_OBJS = $(patsubst %,build/ref_gles3/%,$(REFGL3_OBJS_))
REFGLES3_OBJS += $(patsubst %,build/ref_gles3/%,$(REFGL3_OBJS_GLADEES_))
REFGL4_OBJS = $(patsubst %,build/ref_gl4/%,$(REFGL4_OBJS_))
REFGL4_OBJS += $(patsubst %,build/ref_gl4/%,$(REFGL4_OBJS_GLADE_))
REFSOFT_OBJS = $(patsubst %,build/ref_soft/%,$(REFSOFT_OBJS_))
REFVK_OBJS = $(patsubst %,build/ref_vk/%,$(REFVK_OBJS_))
SERVER_OBJS = $(patsubst %,build/server/%,$(SERVER_OBJS_))
@ -1173,6 +1243,7 @@ GAME_DEPS= $(GAME_OBJS:.o=.d)
REFGL1_DEPS= $(REFGL1_OBJS:.o=.d)
REFGL3_DEPS= $(REFGL3_OBJS:.o=.d)
REFGLES3_DEPS= $(REFGLES3_OBJS:.o=.d)
REFGL4_DEPS= $(REFGL4_OBJS:.o=.d)
REFSOFT_DEPS= $(REFSOFT_OBJS:.o=.d)
REFVK_DEPS= $(REFVK_OBJS:.o=.d)
SERVER_DEPS= $(SERVER_OBJS:.o=.d)
@ -1183,6 +1254,7 @@ SERVER_DEPS= $(SERVER_OBJS:.o=.d)
-include $(REFGL1_DEPS)
-include $(REFGL3_DEPS)
-include $(REFGLES3_DEPS)
-include $(REFGL4_DEPS)
-include $(REFVK_DEPS)
-include $(SERVER_DEPS)
@ -1263,6 +1335,19 @@ release/ref_gles3.so : $(REFGLES3_OBJS)
${Q}$(CC) $(LDFLAGS) $(REFGLES3_OBJS) $(LDLIBS) $(SDLLDFLAGS) -o $@
endif
# release/ref_gl4.so
ifeq ($(YQ2_OSTYPE), Windows)
release/ref_gl4.dll : $(REFGL4_OBJS)
@echo "===> LD $@"
${Q}$(CC) $(LDFLAGS) $(REFGL4_OBJS) $(LDLIBS) $(DLL_SDLLDFLAGS) -o $@
$(Q)strip $@
else
release/ref_gl4.so : $(REFGL4_OBJS)
@echo "===> LD $@"
${Q}$(CC) $(LDFLAGS) $(REFGL4_OBJS) $(LDLIBS) $(SDLLDFLAGS) -o $@
endif
# release/ref_soft.so
ifeq ($(YQ2_OSTYPE), Windows)
release/ref_soft.dll : $(REFSOFT_OBJS)

View file

@ -0,0 +1,413 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (C) 2016-2017 Daniel Gibson
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Drawing of all images that are not textures
*
* =======================================================================
*/
#include "header/local.h"
unsigned d_8to24table[256];
gl4image_t *draw_chars;
static GLuint vbo2D = 0, vao2D = 0, vao2Dcolor = 0; // vao2D is for textured rendering, vao2Dcolor for color-only
void
GL4_Draw_InitLocal(void)
{
/* load console characters */
draw_chars = R_FindPic("conchars", (findimage_t)GL4_FindImage);
if (!draw_chars)
{
ri.Sys_Error(ERR_FATAL, "%s: Couldn't load pics/conchars.pcx",
__func__);
}
// set up attribute layout for 2D textured rendering
glGenVertexArrays(1, &vao2D);
glBindVertexArray(vao2D);
glGenBuffers(1, &vbo2D);
GL4_BindVBO(vbo2D);
GL4_UseProgram(gl4state.si2D.shaderProgram);
glEnableVertexAttribArray(GL4_ATTRIB_POSITION);
// Note: the glVertexAttribPointer() configuration is stored in the VAO, not the shader or sth
// (that's why I use one VAO per 2D shader)
qglVertexAttribPointer(GL4_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), 0);
glEnableVertexAttribArray(GL4_ATTRIB_TEXCOORD);
qglVertexAttribPointer(GL4_ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), 2*sizeof(float));
// set up attribute layout for 2D flat color rendering
glGenVertexArrays(1, &vao2Dcolor);
glBindVertexArray(vao2Dcolor);
GL4_BindVBO(vbo2D); // yes, both VAOs share the same VBO
GL4_UseProgram(gl4state.si2Dcolor.shaderProgram);
glEnableVertexAttribArray(GL4_ATTRIB_POSITION);
qglVertexAttribPointer(GL4_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), 0);
GL4_BindVAO(0);
}
void
GL4_Draw_ShutdownLocal(void)
{
glDeleteBuffers(1, &vbo2D);
vbo2D = 0;
glDeleteVertexArrays(1, &vao2D);
vao2D = 0;
glDeleteVertexArrays(1, &vao2Dcolor);
vao2Dcolor = 0;
}
// bind the texture before calling this
static void
drawTexturedRectangle(float x, float y, float w, float h,
float sl, float tl, float sh, float th)
{
/*
* x,y+h x+w,y+h
* sl,th--------sh,th
* | |
* | |
* | |
* sl,tl--------sh,tl
* x,y x+w,y
*/
GLfloat vBuf[16] = {
// X, Y, S, T
x, y+h, sl, th,
x, y, sl, tl,
x+w, y+h, sh, th,
x+w, y, sh, tl
};
GL4_BindVAO(vao2D);
// Note: while vao2D "remembers" its vbo for drawing, binding the vao does *not*
// implicitly bind the vbo, so I need to explicitly bind it before glBufferData()
GL4_BindVBO(vbo2D);
glBufferData(GL_ARRAY_BUFFER, sizeof(vBuf), vBuf, GL_STREAM_DRAW);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
//glMultiDrawArrays(mode, first, count, drawcount) ??
}
/*
* Draws one 8*8 graphics character with 0 being transparent.
* It can be clipped to the top of the screen to allow the console to be
* smoothly scrolled off.
*/
void
GL4_Draw_CharScaled(int x, int y, int num, float scale)
{
int row, col;
float frow, fcol, size, scaledSize;
num &= 255;
if ((num & 127) == 32)
{
return; /* space */
}
if (y <= -8)
{
return; /* totally off screen */
}
row = num >> 4;
col = num & 15;
frow = row * 0.0625;
fcol = col * 0.0625;
size = 0.0625;
scaledSize = 8*scale;
// TODO: batchen?
GL4_UseProgram(gl4state.si2D.shaderProgram);
GL4_Bind(draw_chars->texnum);
drawTexturedRectangle(x, y, scaledSize, scaledSize, fcol, frow, fcol+size, frow+size);
}
gl4image_t *
GL4_Draw_FindPic(char *name)
{
return R_FindPic(name, (findimage_t)GL4_FindImage);
}
void
GL4_Draw_GetPicSize(int *w, int *h, char *pic)
{
gl4image_t *gl;
gl = R_FindPic(pic, (findimage_t)GL4_FindImage);
if (!gl)
{
*w = *h = -1;
return;
}
*w = gl->width;
*h = gl->height;
}
void
GL4_Draw_StretchPic(int x, int y, int w, int h, char *pic)
{
gl4image_t *gl = R_FindPic(pic, (findimage_t)GL4_FindImage);
if (!gl)
{
R_Printf(PRINT_ALL, "Can't find pic: %s\n", pic);
return;
}
GL4_UseProgram(gl4state.si2D.shaderProgram);
GL4_Bind(gl->texnum);
drawTexturedRectangle(x, y, w, h, gl->sl, gl->tl, gl->sh, gl->th);
}
void
GL4_Draw_PicScaled(int x, int y, char *pic, float factor)
{
gl4image_t *gl = R_FindPic(pic, (findimage_t)GL4_FindImage);
if (!gl)
{
R_Printf(PRINT_ALL, "Can't find pic: %s\n", pic);
return;
}
GL4_UseProgram(gl4state.si2D.shaderProgram);
GL4_Bind(gl->texnum);
drawTexturedRectangle(x, y, gl->width*factor, gl->height*factor, gl->sl, gl->tl, gl->sh, gl->th);
}
/*
* This repeats a 64*64 tile graphic to fill
* the screen around a sized down
* refresh window.
*/
void
GL4_Draw_TileClear(int x, int y, int w, int h, char *pic)
{
gl4image_t *image = R_FindPic(pic, (findimage_t)GL4_FindImage);
if (!image)
{
R_Printf(PRINT_ALL, "Can't find pic: %s\n", pic);
return;
}
GL4_UseProgram(gl4state.si2D.shaderProgram);
GL4_Bind(image->texnum);
drawTexturedRectangle(x, y, w, h, x/64.0f, y/64.0f, (x+w)/64.0f, (y+h)/64.0f);
}
void
GL4_DrawFrameBufferObject(int x, int y, int w, int h, GLuint fboTexture, const float v_blend[4])
{
qboolean underwater = (gl4_newrefdef.rdflags & RDF_UNDERWATER) != 0;
gl4ShaderInfo_t* shader = underwater ? &gl4state.si2DpostProcessWater
: &gl4state.si2DpostProcess;
GL4_UseProgram(shader->shaderProgram);
GL4_Bind(fboTexture);
if(underwater && shader->uniLmScalesOrTime != -1)
{
glUniform1f(shader->uniLmScalesOrTime, gl4_newrefdef.time);
}
if(shader->uniVblend != -1)
{
glUniform4fv(shader->uniVblend, 1, v_blend);
}
drawTexturedRectangle(x, y, w, h, 0, 1, 1, 0);
}
/*
* Fills a box of pixels with a single color
*/
void
GL4_Draw_Fill(int x, int y, int w, int h, int c)
{
union
{
unsigned c;
byte v[4];
} color;
int i;
if ((unsigned)c > 255)
{
ri.Sys_Error(ERR_FATAL, "Draw_Fill: bad color");
}
color.c = d_8to24table[c];
GLfloat vBuf[8] = {
// X, Y
x, y+h,
x, y,
x+w, y+h,
x+w, y
};
for(i=0; i<3; ++i)
{
gl4state.uniCommonData.color.Elements[i] = color.v[i] * (1.0f/255.0f);
}
gl4state.uniCommonData.color.A = 1.0f;
GL4_UpdateUBOCommon();
GL4_UseProgram(gl4state.si2Dcolor.shaderProgram);
GL4_BindVAO(vao2Dcolor);
GL4_BindVBO(vbo2D);
glBufferData(GL_ARRAY_BUFFER, sizeof(vBuf), vBuf, GL_STREAM_DRAW);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
// in GL1 this is called R_Flash() (which just calls R_PolyBlend())
// now implemented in 2D mode and called after SetGL2D() because
// it's pretty similar to GL4_Draw_FadeScreen()
void
GL4_Draw_Flash(const float color[4], float x, float y, float w, float h)
{
if (gl_polyblend->value == 0)
{
return;
}
int i=0;
GLfloat vBuf[8] = {
// X, Y
x, y+h,
x, y,
x+w, y+h,
x+w, y
};
glEnable(GL_BLEND);
for(i=0; i<4; ++i) gl4state.uniCommonData.color.Elements[i] = color[i];
GL4_UpdateUBOCommon();
GL4_UseProgram(gl4state.si2Dcolor.shaderProgram);
GL4_BindVAO(vao2Dcolor);
GL4_BindVBO(vbo2D);
glBufferData(GL_ARRAY_BUFFER, sizeof(vBuf), vBuf, GL_STREAM_DRAW);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisable(GL_BLEND);
}
void
GL4_Draw_FadeScreen(void)
{
float color[4] = {0, 0, 0, 0.6f};
GL4_Draw_Flash(color, 0, 0, vid.width, vid.height);
}
void
GL4_Draw_StretchRaw(int x, int y, int w, int h, int cols, int rows, const byte *data, int bits)
{
int i, j;
GL4_Bind(0);
unsigned image32[320*240]; /* was 256 * 256, but we want a bit more space */
unsigned* img = image32;
if (bits == 32)
{
img = (unsigned *)data;
}
else
{
if(cols*rows > 320*240)
{
/* in case there is a bigger video after all,
* malloc enough space to hold the frame */
img = (unsigned*)malloc(cols*rows*4);
}
for(i=0; i<rows; ++i)
{
int rowOffset = i*cols;
for(j=0; j<cols; ++j)
{
byte palIdx = data[rowOffset+j];
img[rowOffset+j] = gl4_rawpalette[palIdx];
}
}
}
GL4_UseProgram(gl4state.si2D.shaderProgram);
GLuint glTex;
glGenTextures(1, &glTex);
GL4_SelectTMU(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, glTex);
glTexImage2D(GL_TEXTURE_2D, 0, gl4_tex_solid_format,
cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
if(img != image32 && img != (unsigned *)data)
{
free(img);
}
// Note: gl_filter_min could be GL_*_MIPMAP_* so we can't use it for min filter here (=> no mipmaps)
// but gl_filter_max (either GL_LINEAR or GL_NEAREST) should do the trick.
GLint filter = (r_videos_unfiltered->value == 0) ? gl_filter_max : GL_NEAREST;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
drawTexturedRectangle(x, y, w, h, 0.0f, 0.0f, 1.0f, 1.0f);
glDeleteTextures(1, &glTex);
GL4_Bind(0);
}

View file

@ -0,0 +1,845 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (C) 2016-2017 Daniel Gibson
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Texture handling for OpenGL4
*
* =======================================================================
*/
#include "header/local.h"
typedef struct
{
char *name;
int minimize, maximize;
} glmode_t;
glmode_t modes[] = {
{"GL_NEAREST", GL_NEAREST, GL_NEAREST},
{"GL_LINEAR", GL_LINEAR, GL_LINEAR},
{"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST},
{"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR},
{"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST},
{"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR}
};
int gl_filter_min = GL_LINEAR_MIPMAP_NEAREST;
int gl_filter_max = GL_LINEAR;
gl4image_t gl4textures[MAX_GL4TEXTURES];
int numgl4textures = 0;
static int image_max = 0;
void
GL4_TextureMode(char *string)
{
const int num_modes = sizeof(modes)/sizeof(modes[0]);
int i;
for (i = 0; i < num_modes; i++)
{
if (!Q_stricmp(modes[i].name, string))
{
break;
}
}
if (i == num_modes)
{
R_Printf(PRINT_ALL, "bad filter name '%s' (probably from gl_texturemode)\n", string);
return;
}
gl_filter_min = modes[i].minimize;
gl_filter_max = modes[i].maximize;
/* clamp selected anisotropy */
if (gl4config.anisotropic)
{
if (gl_anisotropic->value > gl4config.max_anisotropy)
{
ri.Cvar_SetValue("r_anisotropic", gl4config.max_anisotropy);
}
}
else
{
ri.Cvar_SetValue("r_anisotropic", 0.0);
}
gl4image_t *glt;
const char* nolerplist = gl_nolerp_list->string;
const char* lerplist = r_lerp_list->string;
qboolean unfiltered2D = r_2D_unfiltered->value != 0;
/* change all the existing texture objects */
for (i = 0, glt = gl4textures; i < numgl4textures; i++, glt++)
{
qboolean nolerp = false;
/* r_2D_unfiltered and gl_nolerp_list allow rendering stuff unfiltered even if gl_filter_* is filtered */
if (unfiltered2D && glt->type == it_pic)
{
// exception to that exception: stuff on the r_lerp_list
nolerp = (lerplist== NULL) || (strstr(lerplist, glt->name) == NULL);
}
else if(nolerplist != NULL && strstr(nolerplist, glt->name) != NULL)
{
nolerp = true;
}
GL4_SelectTMU(GL_TEXTURE0);
GL4_Bind(glt->texnum);
if ((glt->type != it_pic) && (glt->type != it_sky)) /* mipmapped texture */
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
/* Set anisotropic filter if supported and enabled */
if (gl4config.anisotropic && gl_anisotropic->value)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, max(gl_anisotropic->value, 1.f));
}
}
else /* texture has no mipmaps */
{
if (nolerp)
{
// this texture shouldn't be filtered at all (no gl_nolerp_list or r_2D_unfiltered case)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
else
{
// we can't use gl_filter_min which might be GL_*_MIPMAP_*
// also, there's no anisotropic filtering for textures w/o mipmaps
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_max);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
}
}
}
}
void
GL4_Bind(GLuint texnum)
{
extern gl4image_t *draw_chars;
if (gl_nobind->value && draw_chars) /* performance evaluation option */
{
texnum = draw_chars->texnum;
}
if (gl4state.currenttexture == texnum)
{
return;
}
gl4state.currenttexture = texnum;
GL4_SelectTMU(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texnum);
}
void
GL4_BindLightmap(int lightmapnum)
{
int i=0;
if(lightmapnum < 0 || lightmapnum >= MAX_LIGHTMAPS)
{
R_Printf(PRINT_ALL, "WARNING: Invalid lightmapnum %i used!\n", lightmapnum);
return;
}
if (gl4state.currentlightmap == lightmapnum)
{
return;
}
gl4state.currentlightmap = lightmapnum;
for(i=0; i<MAX_LIGHTMAPS_PER_SURFACE; ++i)
{
// this assumes that GL_TEXTURE<i+1> = GL_TEXTURE<i> + 1
// at least for GL_TEXTURE0 .. GL_TEXTURE31 that's true
GL4_SelectTMU(GL_TEXTURE1+i);
glBindTexture(GL_TEXTURE_2D, gl4state.lightmap_textureIDs[lightmapnum][i]);
}
}
/*
* Returns has_alpha
*/
qboolean
GL4_Upload32(unsigned *data, int width, int height, qboolean mipmap)
{
qboolean res;
int i;
int c = width * height;
byte *scan = ((byte *)data) + 3;
int comp = gl4_tex_solid_format;
int samples = gl4_solid_format;
for (i = 0; i < c; i++, scan += 4)
{
if (*scan != 255)
{
samples = gl4_alpha_format;
comp = gl4_tex_alpha_format;
break;
}
}
glTexImage2D(GL_TEXTURE_2D, 0, comp, width, height,
0, GL_RGBA, GL_UNSIGNED_BYTE, data);
res = (samples == gl4_alpha_format);
if (mipmap)
{
// TODO: some hardware may require mipmapping disabled for NPOT textures!
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
}
else // if the texture has no mipmaps, we can't use gl_filter_min which might be GL_*_MIPMAP_*
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_max);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
}
if (mipmap && gl4config.anisotropic && gl_anisotropic->value)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, max(gl_anisotropic->value, 1.f));
}
return res;
}
/*
* Returns has_alpha
*/
qboolean
GL4_Upload8(byte *data, int width, int height, qboolean mipmap, qboolean is_sky)
{
int s = width * height;
unsigned *trans = malloc(s * sizeof(unsigned));
for (int i = 0; i < s; i++)
{
int p = data[i];
trans[i] = d_8to24table[p];
/* transparent, so scan around for
another color to avoid alpha fringes */
if (p == 255)
{
if ((i > width) && (data[i - width] != 255))
{
p = data[i - width];
}
else if ((i < s - width) && (data[i + width] != 255))
{
p = data[i + width];
}
else if ((i > 0) && (data[i - 1] != 255))
{
p = data[i - 1];
}
else if ((i < s - 1) && (data[i + 1] != 255))
{
p = data[i + 1];
}
else
{
p = 0;
}
/* copy rgb components */
((byte *)&trans[i])[0] = ((byte *)&d_8to24table[p])[0];
((byte *)&trans[i])[1] = ((byte *)&d_8to24table[p])[1];
((byte *)&trans[i])[2] = ((byte *)&d_8to24table[p])[2];
}
}
qboolean ret = GL4_Upload32(trans, width, height, mipmap);
free(trans);
return ret;
}
typedef struct
{
short x, y;
} floodfill_t;
/* must be a power of 2 */
#define FLOODFILL_FIFO_SIZE 0x1000
#define FLOODFILL_FIFO_MASK (FLOODFILL_FIFO_SIZE - 1)
#define FLOODFILL_STEP(off, dx, dy) \
{ \
if (pos[off] == fillcolor) \
{ \
pos[off] = 255; \
fifo[inpt].x = x + (dx), fifo[inpt].y = y + (dy); \
inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; \
} \
else if (pos[off] != 255) \
{ \
fdc = pos[off]; \
} \
}
/*
* Fill background pixels so mipmapping doesn't have haloes
*/
static void
FloodFillSkin(byte *skin, int skinwidth, int skinheight)
{
byte fillcolor = *skin; /* assume this is the pixel to fill */
floodfill_t fifo[FLOODFILL_FIFO_SIZE];
int inpt = 0, outpt = 0;
int filledcolor = 0;
int i;
/* attempt to find opaque black */
for (i = 0; i < 256; ++i)
{
if (LittleLong(d_8to24table[i]) == (255 << 0)) /* alpha 1.0 */
{
filledcolor = i;
break;
}
}
/* can't fill to filled color or to transparent color (used as visited marker) */
if ((fillcolor == filledcolor) || (fillcolor == 255))
{
return;
}
fifo[inpt].x = 0, fifo[inpt].y = 0;
inpt = (inpt + 1) & FLOODFILL_FIFO_MASK;
while (outpt != inpt)
{
int x = fifo[outpt].x, y = fifo[outpt].y;
int fdc = filledcolor;
byte *pos = &skin[x + skinwidth * y];
outpt = (outpt + 1) & FLOODFILL_FIFO_MASK;
if (x > 0)
{
FLOODFILL_STEP(-1, -1, 0);
}
if (x < skinwidth - 1)
{
FLOODFILL_STEP(1, 1, 0);
}
if (y > 0)
{
FLOODFILL_STEP(-skinwidth, 0, -1);
}
if (y < skinheight - 1)
{
FLOODFILL_STEP(skinwidth, 0, 1);
}
skin[x + skinwidth * y] = fdc;
}
}
/*
* This is also used as an entry point for the generated r_notexture
*/
gl4image_t *
GL4_LoadPic(char *name, byte *pic, int width, int realwidth,
int height, int realheight, size_t data_size,
imagetype_t type, int bits)
{
gl4image_t *image = NULL;
GLuint texNum=0;
int i;
qboolean nolerp = false;
if (r_2D_unfiltered->value && type == it_pic)
{
// if r_2D_unfiltered is true(ish), nolerp should usually be true,
// *unless* the texture is on the r_lerp_list
nolerp = (r_lerp_list->string == NULL) || (strstr(r_lerp_list->string, name) == NULL);
}
else if (gl_nolerp_list != NULL && gl_nolerp_list->string != NULL)
{
nolerp = strstr(gl_nolerp_list->string, name) != NULL;
}
/* find a free gl4image_t */
for (i = 0, image = gl4textures; i < numgl4textures; i++, image++)
{
if (image->texnum == 0)
{
break;
}
}
if (i == numgl4textures)
{
if (numgl4textures == MAX_GL4TEXTURES)
{
ri.Sys_Error(ERR_DROP, "MAX_GLTEXTURES");
}
numgl4textures++;
}
image = &gl4textures[i];
if (strlen(name) >= sizeof(image->name))
{
ri.Sys_Error(ERR_DROP, "%s: \"%s\" is too long", __func__, name);
}
strcpy(image->name, name);
image->registration_sequence = registration_sequence;
image->width = width;
image->height = height;
image->type = type;
if ((type == it_skin) && (bits == 8))
{
FloodFillSkin(pic, width, height);
}
image->is_lava = (strstr(name, "lava") != NULL);
// image->scrap = false; // TODO: reintroduce scrap? would allow optimizations in 2D rendering..
glGenTextures(1, &texNum);
image->texnum = texNum;
GL4_SelectTMU(GL_TEXTURE0);
GL4_Bind(texNum);
if (bits == 8)
{
// resize 8bit images only when we forced such logic
if (r_scale8bittextures->value)
{
byte *image_converted;
int scale = 2;
// scale 3 times if lerp image
if (!nolerp && (vid.height >= 240 * 3))
scale = 3;
image_converted = malloc(width * height * scale * scale);
if (!image_converted)
return NULL;
if (scale == 3) {
scale3x(pic, image_converted, width, height);
} else {
scale2x(pic, image_converted, width, height);
}
image->has_alpha = GL4_Upload8(image_converted, width * scale, height * scale,
(image->type != it_pic && image->type != it_sky),
image->type == it_sky);
free(image_converted);
}
else
{
image->has_alpha = GL4_Upload8(pic, width, height,
(image->type != it_pic && image->type != it_sky),
image->type == it_sky);
}
}
else
{
image->has_alpha = GL4_Upload32((unsigned *)pic, width, height,
(image->type != it_pic && image->type != it_sky));
}
if (realwidth && realheight)
{
if ((realwidth <= image->width) && (realheight <= image->height))
{
image->width = realwidth;
image->height = realheight;
}
else
{
R_Printf(PRINT_DEVELOPER,
"Warning, image '%s' has hi-res replacement smaller than the original! (%d x %d) < (%d x %d)\n",
name, image->width, image->height, realwidth, realheight);
}
}
image->sl = 0;
image->sh = 1;
image->tl = 0;
image->th = 1;
if (nolerp)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
#if 0 // TODO: the scrap could allow batch rendering 2D stuff? not sure it's worth the hassle..
/* load little pics into the scrap */
if (!nolerp && (image->type == it_pic) && (bits == 8) &&
(image->width < 64) && (image->height < 64))
{
int x, y;
int i, j, k;
int texnum;
texnum = Scrap_AllocBlock(image->width, image->height, &x, &y);
if (texnum == -1)
{
goto nonscrap;
}
scrap_dirty = true;
/* copy the texels into the scrap block */
k = 0;
for (i = 0; i < image->height; i++)
{
for (j = 0; j < image->width; j++, k++)
{
scrap_texels[texnum][(y + i) * BLOCK_WIDTH + x + j] = pic[k];
}
}
image->texnum = TEXNUM_SCRAPS + texnum;
image->scrap = true;
image->has_alpha = true;
image->sl = (x + 0.01) / (float)BLOCK_WIDTH;
image->sh = (x + image->width - 0.01) / (float)BLOCK_WIDTH;
image->tl = (y + 0.01) / (float)BLOCK_WIDTH;
image->th = (y + image->height - 0.01) / (float)BLOCK_WIDTH;
}
else
{
nonscrap:
image->scrap = false;
image->texnum = TEXNUM_IMAGES + (image - gltextures);
R_Bind(image->texnum);
if (bits == 8)
{
image->has_alpha = R_Upload8(pic, width, height,
(image->type != it_pic && image->type != it_sky),
image->type == it_sky);
}
else
{
image->has_alpha = R_Upload32((unsigned *)pic, width, height,
(image->type != it_pic && image->type != it_sky));
}
image->upload_width = upload_width; /* after power of 2 and scales */
image->upload_height = upload_height;
image->paletted = uploaded_paletted;
if (realwidth && realheight)
{
if ((realwidth <= image->width) && (realheight <= image->height))
{
image->width = realwidth;
image->height = realheight;
}
else
{
R_Printf(PRINT_DEVELOPER,
"Warning, image '%s' has hi-res replacement smaller than the original! (%d x %d) < (%d x %d)\n",
name, image->width, image->height, realwidth, realheight);
}
}
image->sl = 0;
image->sh = 1;
image->tl = 0;
image->th = 1;
if (nolerp)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
}
#endif // 0
return image;
}
/*
* Finds or loads the given image or NULL
*/
gl4image_t *
GL4_FindImage(const char *name, imagetype_t type)
{
gl4image_t *image;
int i, len;
char *ptr;
char namewe[256];
const char* ext;
if (!name)
{
return NULL;
}
ext = COM_FileExtension(name);
if(!ext[0])
{
/* file has no extension */
return NULL;
}
len = strlen(name);
/* Remove the extension */
memset(namewe, 0, 256);
memcpy(namewe, name, len - (strlen(ext) + 1));
if (len < 5)
{
return NULL;
}
/* fix backslashes */
while ((ptr = strchr(name, '\\')))
{
*ptr = '/';
}
/* look for it */
for (i = 0, image = gl4textures; i < numgl4textures; i++, image++)
{
if (!strcmp(name, image->name))
{
image->registration_sequence = registration_sequence;
return image;
}
}
//
// load the pic from disk
//
image = (gl4image_t *)R_LoadImage(name, namewe, ext, type,
r_retexturing->value, (loadimage_t)GL4_LoadPic);
if (!image && r_validation->value)
{
R_Printf(PRINT_ALL, "%s: can't load %s\n", __func__, name);
}
return image;
}
gl4image_t *
GL4_RegisterSkin(char *name)
{
return GL4_FindImage(name, it_skin);
}
/*
* Any image that was not touched on
* this registration sequence
* will be freed.
*/
void
GL4_FreeUnusedImages(void)
{
int i;
gl4image_t *image;
/* never free r_notexture or particle texture */
gl4_notexture->registration_sequence = registration_sequence;
gl4_particletexture->registration_sequence = registration_sequence;
for (i = 0, image = gl4textures; i < numgl4textures; i++, image++)
{
if (image->registration_sequence == registration_sequence)
{
continue; /* used this sequence */
}
if (!image->registration_sequence)
{
continue; /* free image_t slot */
}
if (image->type == it_pic)
{
continue; /* don't free pics */
}
/* free it */
glDeleteTextures(1, &image->texnum);
memset(image, 0, sizeof(*image));
}
}
qboolean
GL4_ImageHasFreeSpace(void)
{
int i, used;
gl4image_t *image;
used = 0;
for (i = 0, image = gl4textures; i < numgl4textures; i++, image++)
{
if (!image->name[0])
continue;
if (image->registration_sequence == registration_sequence)
{
used ++;
}
}
if (image_max < used)
{
image_max = used;
}
// should same size of free slots as currently used
return (numgl4textures + used) < MAX_GL4TEXTURES;
}
void
GL4_ShutdownImages(void)
{
int i;
gl4image_t *image;
for (i = 0, image = gl4textures; i < numgl4textures; i++, image++)
{
if (!image->registration_sequence)
{
continue; /* free image_t slot */
}
/* free it */
glDeleteTextures(1, &image->texnum);
memset(image, 0, sizeof(*image));
}
}
static qboolean IsNPOT(int v)
{
unsigned int uv = v;
// just try all the power of two values between 1 and 1 << 15 (32k)
for(unsigned int i=0; i<16; ++i)
{
unsigned int pot = (1u << i);
if(uv & pot)
{
return uv != pot;
}
}
return true;
}
void
GL4_ImageList_f(void)
{
int i, used, texels;
qboolean freeup;
gl4image_t *image;
const char *formatstrings[2] = {
"RGB ",
"RGBA"
};
const char* potstrings[2] = {
" POT", "NPOT"
};
R_Printf(PRINT_ALL, "------------------\n");
texels = 0;
used = 0;
for (i = 0, image = gl4textures; i < numgl4textures; i++, image++)
{
int w, h;
char *in_use = "";
qboolean isNPOT = false;
if (image->texnum == 0)
{
continue;
}
if (image->registration_sequence == registration_sequence)
{
in_use = "*";
used++;
}
w = image->width;
h = image->height;
isNPOT = IsNPOT(w) || IsNPOT(h);
texels += w*h;
char imageType = '?';
switch (image->type)
{
case it_skin:
imageType = 'M';
break;
case it_sprite:
imageType = 'S';
break;
case it_wall:
imageType = 'W';
break;
case it_pic:
imageType = 'P';
break;
case it_sky:
imageType = 'Y';
break;
default:
imageType = '?';
break;
}
char isLava = image->is_lava ? 'L' : ' ';
R_Printf(PRINT_ALL, "%c%c %3i %3i %s %s: %s %s\n", imageType, isLava, w, h,
formatstrings[image->has_alpha], potstrings[isNPOT], image->name, in_use);
}
R_Printf(PRINT_ALL, "Total texel count (not counting mipmaps): %i\n", texels);
freeup = GL4_ImageHasFreeSpace();
R_Printf(PRINT_ALL, "Used %d of %d images%s.\n", used, image_max, freeup ? ", has free space" : "");
}

View file

@ -0,0 +1,403 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (C) 2016-2017 Daniel Gibson
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Lightmaps and dynamic lighting
*
* =======================================================================
*/
#include "header/local.h"
extern gl4lightmapstate_t gl4_lms;
int r_dlightframecount;
static vec3_t pointcolor;
static cplane_t *lightplane; /* used as shadow plane */
vec3_t lightspot;
void
GL4_MarkSurfaceLights(dlight_t *light, int bit, mnode_t *node, int r_dlightframecount)
{
msurface_t *surf;
int i;
/* mark the polygons */
surf = gl4_worldmodel->surfaces + node->firstsurface;
for (i = 0; i < node->numsurfaces; i++, surf++)
{
int sidebit;
float dist;
if (surf->dlightframe != r_dlightframecount)
{
surf->dlightbits = 0;
surf->dlightframe = r_dlightframecount;
}
dist = DotProduct(light->origin, surf->plane->normal) - surf->plane->dist;
if (dist >= 0)
{
sidebit = 0;
}
else
{
sidebit = SURF_PLANEBACK;
}
if ((surf->flags & SURF_PLANEBACK) != sidebit)
{
continue;
}
surf->dlightbits |= bit;
}
}
void
GL4_PushDlights(void)
{
int i;
dlight_t *l;
/* because the count hasn't advanced yet for this frame */
r_dlightframecount = gl4_framecount + 1;
l = gl4_newrefdef.dlights;
gl4state.uniLightsData.numDynLights = gl4_newrefdef.num_dlights;
for (i = 0; i < gl4_newrefdef.num_dlights; i++, l++)
{
gl4UniDynLight* udl = &gl4state.uniLightsData.dynLights[i];
R_MarkLights(l, 1 << i, gl4_worldmodel->nodes, r_dlightframecount, GL4_MarkSurfaceLights);
VectorCopy(l->origin, udl->origin);
VectorCopy(l->color, udl->color);
udl->intensity = l->intensity;
}
assert(MAX_DLIGHTS == 32 && "If MAX_DLIGHTS changes, remember to adjust the uniform buffer definition in the shader!");
if(i < MAX_DLIGHTS)
{
memset(&gl4state.uniLightsData.dynLights[i], 0, (MAX_DLIGHTS-i)*sizeof(gl4state.uniLightsData.dynLights[0]));
}
GL4_UpdateUBOLights();
}
static int
RecursiveLightPoint(mnode_t *node, vec3_t start, vec3_t end)
{
float front, back, frac;
int side;
cplane_t *plane;
vec3_t mid;
msurface_t *surf;
int s, t, ds, dt;
int i;
mtexinfo_t *tex;
byte *lightmap;
int maps;
int r;
if (node->contents != CONTENTS_NODE)
{
return -1; /* didn't hit anything */
}
/* calculate mid point */
plane = node->plane;
front = DotProduct(start, plane->normal) - plane->dist;
back = DotProduct(end, plane->normal) - plane->dist;
side = front < 0;
if ((back < 0) == side)
{
return RecursiveLightPoint(node->children[side], start, end);
}
frac = front / (front - back);
mid[0] = start[0] + (end[0] - start[0]) * frac;
mid[1] = start[1] + (end[1] - start[1]) * frac;
mid[2] = start[2] + (end[2] - start[2]) * frac;
/* go down front side */
r = RecursiveLightPoint(node->children[side], start, mid);
if (r >= 0)
{
return r; /* hit something */
}
if ((back < 0) == side)
{
return -1; /* didn't hit anuthing */
}
/* check for impact on this node */
VectorCopy(mid, lightspot);
lightplane = plane;
surf = gl4_worldmodel->surfaces + node->firstsurface;
for (i = 0; i < node->numsurfaces; i++, surf++)
{
if (surf->flags & (SURF_DRAWTURB | SURF_DRAWSKY))
{
continue; /* no lightmaps */
}
tex = surf->texinfo;
s = DotProduct(mid, tex->vecs[0]) + tex->vecs[0][3];
t = DotProduct(mid, tex->vecs[1]) + tex->vecs[1][3];
if ((s < surf->texturemins[0]) ||
(t < surf->texturemins[1]))
{
continue;
}
ds = s - surf->texturemins[0];
dt = t - surf->texturemins[1];
if ((ds > surf->extents[0]) || (dt > surf->extents[1]))
{
continue;
}
if (!surf->samples)
{
return 0;
}
ds >>= 4;
dt >>= 4;
lightmap = surf->samples;
VectorCopy(vec3_origin, pointcolor);
lightmap += 3 * (dt * ((surf->extents[0] >> 4) + 1) + ds);
for (maps = 0; maps < MAX_LIGHTMAPS_PER_SURFACE && surf->styles[maps] != 255;
maps++)
{
const float *rgb;
int j;
rgb = gl4_newrefdef.lightstyles[surf->styles[maps]].rgb;
/* Apply light level to models */
for (j = 0; j < 3; j++)
{
float scale;
scale = rgb[j] * r_modulate->value;
pointcolor[j] += lightmap[j] * scale * (1.0 / 255);
}
lightmap += 3 * ((surf->extents[0] >> 4) + 1) *
((surf->extents[1] >> 4) + 1);
}
return 1;
}
/* go down back side */
return RecursiveLightPoint(node->children[!side], mid, end);
}
void
GL4_LightPoint(entity_t *currententity, vec3_t p, vec3_t color)
{
vec3_t end;
float r;
int lnum;
dlight_t *dl;
vec3_t dist;
float add;
if (!gl4_worldmodel->lightdata || !currententity)
{
color[0] = color[1] = color[2] = 1.0;
return;
}
end[0] = p[0];
end[1] = p[1];
end[2] = p[2] - 2048;
// TODO: don't just aggregate the color, but also save position of brightest+nearest light
// for shadow position and maybe lighting on model?
r = RecursiveLightPoint(gl4_worldmodel->nodes, p, end);
if (r == -1)
{
VectorCopy(vec3_origin, color);
}
else
{
VectorCopy(pointcolor, color);
}
/* add dynamic lights */
dl = gl4_newrefdef.dlights;
for (lnum = 0; lnum < gl4_newrefdef.num_dlights; lnum++, dl++)
{
VectorSubtract(currententity->origin,
dl->origin, dist);
add = dl->intensity - VectorLength(dist);
add *= (1.0f / 256.0f);
if (add > 0)
{
VectorMA(color, add, dl->color, color);
}
}
VectorScale(color, r_modulate->value, color);
}
/*
* Combine and scale multiple lightmaps into the floating format in blocklights
*/
void
GL4_BuildLightMap(msurface_t *surf, int offsetInLMbuf, int stride)
{
int smax, tmax;
int r, g, b, a, max;
int i, j, size, map, numMaps;
byte *lightmap;
if (surf->texinfo->flags &
(SURF_SKY | SURF_TRANS33 | SURF_TRANS66 | SURF_WARP))
{
ri.Sys_Error(ERR_DROP, "GL4_BuildLightMap called for non-lit surface");
}
smax = (surf->extents[0] >> 4) + 1;
tmax = (surf->extents[1] >> 4) + 1;
size = smax * tmax;
stride -= (smax << 2);
if (size > 34*34*3)
{
ri.Sys_Error(ERR_DROP, "Bad s_blocklights size");
}
// count number of lightmaps surf actually has
for (numMaps = 0; numMaps < MAX_LIGHTMAPS_PER_SURFACE && surf->styles[numMaps] != 255; ++numMaps)
{}
if (!surf->samples)
{
// no lightmap samples? set at least one lightmap to fullbright, rest to 0 as normal
if (numMaps == 0) numMaps = 1; // make sure at least one lightmap is set to fullbright
for (map = 0; map < MAX_LIGHTMAPS_PER_SURFACE; ++map)
{
// we always create 4 (MAX_LIGHTMAPS_PER_SURFACE) lightmaps.
// if surf has less (numMaps < 4), the remaining ones are zeroed out.
// this makes sure that all 4 lightmap textures in gl4state.lightmap_textureIDs[i] have the same layout
// and the shader can use the same texture coordinates for all of them
int c = (map < numMaps) ? 255 : 0;
byte* dest = gl4_lms.lightmap_buffers[map] + offsetInLMbuf;
for (i = 0; i < tmax; i++, dest += stride)
{
memset(dest, c, 4*smax);
dest += 4*smax;
}
}
return;
}
/* add all the lightmaps */
// Note: dynamic lights aren't handled here anymore, they're handled in the shader
// as we don't apply scale here anymore, nor blend the numMaps lightmaps together,
// the code has gotten a lot easier and we can copy directly from surf->samples to dest
// without converting to float first etc
lightmap = surf->samples;
for(map=0; map<numMaps; ++map)
{
byte* dest = gl4_lms.lightmap_buffers[map] + offsetInLMbuf;
int idxInLightmap = 0;
for (i = 0; i < tmax; i++, dest += stride)
{
for (j = 0; j < smax; j++)
{
r = lightmap[idxInLightmap * 3 + 0];
g = lightmap[idxInLightmap * 3 + 1];
b = lightmap[idxInLightmap * 3 + 2];
/* determine the brightest of the three color components */
if (r > g) max = r;
else max = g;
if (b > max) max = b;
/* alpha is ONLY used for the mono lightmap case. For this
reason we set it to the brightest of the color components
so that things don't get too dim. */
a = max;
dest[0] = r;
dest[1] = g;
dest[2] = b;
dest[3] = a;
dest += 4;
++idxInLightmap;
}
}
lightmap += size * 3; /* skip to next lightmap */
}
for ( ; map < MAX_LIGHTMAPS_PER_SURFACE; ++map)
{
// like above, fill up remaining lightmaps with 0
byte* dest = gl4_lms.lightmap_buffers[map] + offsetInLMbuf;
for (i = 0; i < tmax; i++, dest += stride)
{
memset(dest, 0, 4*smax);
dest += 4*smax;
}
}
}

View file

@ -0,0 +1,268 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (C) 2016-2017 Daniel Gibson
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Lightmap handling
*
* =======================================================================
*/
#include "header/local.h"
#define TEXNUM_LIGHTMAPS 1024
extern gl4lightmapstate_t gl4_lms;
void
GL4_LM_InitBlock(void)
{
memset(gl4_lms.allocated, 0, sizeof(gl4_lms.allocated));
}
void
GL4_LM_UploadBlock(void)
{
int map;
// NOTE: we don't use the dynamic lightmap anymore - all lightmaps are loaded at level load
// and not changed after that. they're blended dynamically depending on light styles
// though, and dynamic lights are (will be) applied in shader, hopefully per fragment.
GL4_BindLightmap(gl4_lms.current_lightmap_texture);
// upload all 4 lightmaps
for(map=0; map < MAX_LIGHTMAPS_PER_SURFACE; ++map)
{
GL4_SelectTMU(GL_TEXTURE1+map); // this relies on GL_TEXTURE2 being GL_TEXTURE1+1 etc
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl4_lms.internal_format = GL_LIGHTMAP_FORMAT;
glTexImage2D(GL_TEXTURE_2D, 0, gl4_lms.internal_format,
BLOCK_WIDTH, BLOCK_HEIGHT, 0, GL_LIGHTMAP_FORMAT,
GL_UNSIGNED_BYTE, gl4_lms.lightmap_buffers[map]);
}
if (++gl4_lms.current_lightmap_texture == MAX_LIGHTMAPS)
{
ri.Sys_Error(ERR_DROP, "LM_UploadBlock() - MAX_LIGHTMAPS exceeded\n");
}
}
/*
* returns a texture number and the position inside it
*/
qboolean
GL4_LM_AllocBlock(int w, int h, int *x, int *y)
{
int i, j;
int best, best2;
best = BLOCK_HEIGHT;
for (i = 0; i < BLOCK_WIDTH - w; i++)
{
best2 = 0;
for (j = 0; j < w; j++)
{
if (gl4_lms.allocated[i + j] >= best)
{
break;
}
if (gl4_lms.allocated[i + j] > best2)
{
best2 = gl4_lms.allocated[i + j];
}
}
if (j == w)
{
/* this is a valid spot */
*x = i;
*y = best = best2;
}
}
if (best + h > BLOCK_HEIGHT)
{
return false;
}
for (i = 0; i < w; i++)
{
gl4_lms.allocated[*x + i] = best + h;
}
return true;
}
void
GL4_LM_BuildPolygonFromSurface(gl4model_t *currentmodel, msurface_t *fa)
{
int i, lindex, lnumverts;
medge_t *pedges, *r_pedge;
float *vec;
float s, t;
glpoly_t *poly;
vec3_t total;
vec3_t normal;
/* reconstruct the polygon */
pedges = currentmodel->edges;
lnumverts = fa->numedges;
VectorClear(total);
/* draw texture */
poly = Hunk_Alloc(sizeof(glpoly_t) +
(lnumverts - 4) * sizeof(gl4_3D_vtx_t));
poly->next = fa->polys;
poly->flags = fa->flags;
fa->polys = poly;
poly->numverts = lnumverts;
VectorCopy(fa->plane->normal, normal);
if(fa->flags & SURF_PLANEBACK)
{
// if for some reason the normal sticks to the back of the plane, invert it
// so it's usable for the shader
for (i=0; i<3; ++i) normal[i] = -normal[i];
}
for (i = 0; i < lnumverts; i++)
{
gl4_3D_vtx_t* vert = &poly->vertices[i];
lindex = currentmodel->surfedges[fa->firstedge + i];
if (lindex > 0)
{
r_pedge = &pedges[lindex];
vec = currentmodel->vertexes[r_pedge->v[0]].position;
}
else
{
r_pedge = &pedges[-lindex];
vec = currentmodel->vertexes[r_pedge->v[1]].position;
}
s = DotProduct(vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3];
s /= fa->texinfo->image->width;
t = DotProduct(vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3];
t /= fa->texinfo->image->height;
VectorAdd(total, vec, total);
VectorCopy(vec, vert->pos);
vert->texCoord[0] = s;
vert->texCoord[1] = t;
/* lightmap texture coordinates */
s = DotProduct(vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3];
s -= fa->texturemins[0];
s += fa->light_s * 16;
s += 8;
s /= BLOCK_WIDTH * 16; /* fa->texinfo->texture->width; */
t = DotProduct(vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3];
t -= fa->texturemins[1];
t += fa->light_t * 16;
t += 8;
t /= BLOCK_HEIGHT * 16; /* fa->texinfo->texture->height; */
vert->lmTexCoord[0] = s;
vert->lmTexCoord[1] = t;
VectorCopy(normal, vert->normal);
vert->lightFlags = 0;
}
}
void
GL4_LM_CreateSurfaceLightmap(msurface_t *surf)
{
int smax, tmax;
if (surf->flags & (SURF_DRAWSKY | SURF_DRAWTURB))
{
return;
}
smax = (surf->extents[0] >> 4) + 1;
tmax = (surf->extents[1] >> 4) + 1;
if (!GL4_LM_AllocBlock(smax, tmax, &surf->light_s, &surf->light_t))
{
GL4_LM_UploadBlock();
GL4_LM_InitBlock();
if (!GL4_LM_AllocBlock(smax, tmax, &surf->light_s, &surf->light_t))
{
ri.Sys_Error(ERR_FATAL, "Consecutive calls to LM_AllocBlock(%d,%d) failed\n",
smax, tmax);
}
}
surf->lightmaptexturenum = gl4_lms.current_lightmap_texture;
GL4_BuildLightMap(surf, (surf->light_t * BLOCK_WIDTH + surf->light_s) * LIGHTMAP_BYTES, BLOCK_WIDTH * LIGHTMAP_BYTES);
}
void
GL4_LM_BeginBuildingLightmaps(gl4model_t *m)
{
static lightstyle_t lightstyles[MAX_LIGHTSTYLES];
int i;
memset(gl4_lms.allocated, 0, sizeof(gl4_lms.allocated));
gl4_framecount = 1; /* no dlightcache */
/* setup the base lightstyles so the lightmaps
won't have to be regenerated the first time
they're seen */
for (i = 0; i < MAX_LIGHTSTYLES; i++)
{
lightstyles[i].rgb[0] = 1;
lightstyles[i].rgb[1] = 1;
lightstyles[i].rgb[2] = 1;
lightstyles[i].white = 3;
}
gl4_newrefdef.lightstyles = lightstyles;
gl4_lms.current_lightmap_texture = 0;
gl4_lms.internal_format = GL_LIGHTMAP_FORMAT;
// Note: the dynamic lightmap used to be initialized here, we don't use that anymore.
}
void
GL4_LM_EndBuildingLightmaps(void)
{
GL4_LM_UploadBlock();
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,171 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (C) 2016-2017 Daniel Gibson
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Misc OpenGL4 refresher functions
*
* =======================================================================
*/
#include "header/local.h"
gl4image_t *gl4_notexture; /* use for bad textures */
gl4image_t *gl4_particletexture; /* little dot for particles */
void
GL4_SetDefaultState(void)
{
glClearColor(1, 0, 0.5, 0.5);
#ifndef YQ2_GL3_GLES
// in GLES this is only supported with an extension:
// https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_multisample_compatibility.txt
// but apparently it's just enabled by default if set in the context?
glDisable(GL_MULTISAMPLE);
#endif
glCullFace(GL_FRONT);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glDisable(GL_BLEND);
#ifndef YQ2_GL3_GLES
// in GLES GL_FILL is the only supported mode
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
// TODO: gl1_texturealphamode?
GL4_TextureMode(gl_texturemode->string);
//R_TextureAlphaMode(gl1_texturealphamode->string);
//R_TextureSolidMode(gl1_texturesolidmode->string);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#ifndef YQ2_GL3_GLES // see above
if (gl_msaa_samples->value)
{
glEnable(GL_MULTISAMPLE);
// glHint(GL_MULTISAMPLE_FILTER_HINT_NV, GL_NICEST); TODO what is this for?
}
#endif
}
static byte dottexture[8][8] = {
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, 0, 0, 0, 0},
{0, 1, 1, 1, 1, 0, 0, 0},
{0, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 1, 1, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
};
void
GL4_InitParticleTexture(void)
{
int x, y;
byte data[8][8][4];
/* particle texture */
for (x = 0; x < 8; x++)
{
for (y = 0; y < 8; y++)
{
data[y][x][0] = 255;
data[y][x][1] = 255;
data[y][x][2] = 255;
data[y][x][3] = dottexture[x][y] * 255;
}
}
gl4_particletexture = GL4_LoadPic("***particle***", (byte *)data,
8, 0, 8, 0, 8 * 8, it_sprite, 32);
/* also use this for bad textures, but without alpha */
for (x = 0; x < 8; x++)
{
for (y = 0; y < 8; y++)
{
data[y][x][0] = dottexture[x & 3][y & 3] * 255;
data[y][x][1] = 0;
data[y][x][2] = 0;
data[y][x][3] = 255;
}
}
gl4_notexture = GL4_LoadPic("***r_notexture***", (byte *)data,
8, 0, 8, 0, 8 * 8, it_wall, 32);
}
void
GL4_ScreenShot(void)
{
int w=vid.width, h=vid.height;
#ifdef YQ2_GL3_GLES
// My RPi4's GLES3 doesn't like GL_RGB, so use GL_RGBA with GLES
// TODO: we could convert the screenshot to RGB before writing
// so the resulting file is smaller
static const int comps = 4;
#else // Desktop GL
static const int comps = 3;
#endif
byte *buffer = malloc(w*h*comps);
if (!buffer)
{
R_Printf(PRINT_ALL, "GL4_ScreenShot: Couldn't malloc %d bytes\n", w*h*3);
return;
}
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, w, h, (comps == 4) ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, buffer);
// the pixels are now row-wise left to right, bottom to top,
// but we need them row-wise left to right, top to bottom.
// so swap bottom rows with top rows
{
size_t bytesPerRow = comps*w;
YQ2_VLA(byte, rowBuffer, bytesPerRow);
byte *curRowL = buffer; // first byte of first row
byte *curRowH = buffer + bytesPerRow*(h-1); // first byte of last row
while(curRowL < curRowH)
{
memcpy(rowBuffer, curRowL, bytesPerRow);
memcpy(curRowL, curRowH, bytesPerRow);
memcpy(curRowH, rowBuffer, bytesPerRow);
curRowL += bytesPerRow;
curRowH -= bytesPerRow;
}
YQ2_VLAFREE(rowBuffer);
}
ri.Vid_WriteScreenshot(w, h, comps, buffer);
free(buffer);
}

View file

@ -0,0 +1,834 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (C) 2016-2017 Daniel Gibson
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Model loading and caching for OpenGL4. Includes the .bsp file format
*
* =======================================================================
*/
#include "header/local.h"
enum { MAX_MOD_KNOWN = 512 };
YQ2_ALIGNAS_TYPE(int) static byte mod_novis[MAX_MAP_LEAFS / 8];
gl4model_t mod_known[MAX_MOD_KNOWN];
static int mod_numknown;
static int mod_max = 0;
int registration_sequence;
//===============================================================================
static qboolean
Mod_HasFreeSpace(void)
{
int i, used;
gl4model_t *mod;
used = 0;
for (i=0, mod=mod_known ; i < mod_numknown ; i++, mod++)
{
if (!mod->name[0])
continue;
if (mod->registration_sequence == registration_sequence)
{
used ++;
}
}
if (mod_max < used)
{
mod_max = used;
}
// should same size of free slots as currently used
return (mod_numknown + mod_max) < MAX_MOD_KNOWN;
}
const byte*
GL4_Mod_ClusterPVS(int cluster, const gl4model_t *model)
{
if ((cluster == -1) || !model->vis)
{
return mod_novis;
}
return Mod_DecompressVis((byte *)model->vis +
model->vis->bitofs[cluster][DVIS_PVS],
(model->vis->numclusters + 7) >> 3);
}
void
GL4_Mod_Modellist_f(void)
{
int i, total, used;
gl4model_t *mod;
qboolean freeup;
total = 0;
used = 0;
R_Printf(PRINT_ALL, "Loaded models:\n");
for (i = 0, mod = mod_known; i < mod_numknown; i++, mod++)
{
char *in_use = "";
if (mod->registration_sequence == registration_sequence)
{
in_use = "*";
used ++;
}
if (!mod->name[0])
{
continue;
}
R_Printf(PRINT_ALL, "%8i : %s %s\n",
mod->extradatasize, mod->name, in_use);
total += mod->extradatasize;
}
R_Printf(PRINT_ALL, "Total resident: %i\n", total);
// update statistics
freeup = Mod_HasFreeSpace();
R_Printf(PRINT_ALL, "Used %d of %d models%s.\n", used, mod_max, freeup ? ", has free space" : "");
}
void
GL4_Mod_Init(void)
{
mod_max = 0;
memset(mod_novis, 0xff, sizeof(mod_novis));
}
static void
Mod_LoadSubmodels(gl4model_t *loadmodel, byte *mod_base, lump_t *l)
{
dmodel_t *in;
gl4model_t *out;
int i, j, count;
in = (void *)(mod_base + l->fileofs);
if (l->filelen % sizeof(*in))
{
ri.Sys_Error(ERR_DROP, "%s: funny lump size in %s",
__func__, loadmodel->name);
}
count = l->filelen / sizeof(*in);
out = Hunk_Alloc(count * sizeof(*out));
loadmodel->submodels = out;
loadmodel->numsubmodels = count;
for (i = 0; i < count; i++, in++, out++)
{
if (i == 0)
{
// copy parent as template for first model
memcpy(out, loadmodel, sizeof(*out));
}
else
{
// copy first as template for model
memcpy(out, loadmodel->submodels, sizeof(*out));
}
Com_sprintf (out->name, sizeof(out->name), "*%d", i);
for (j = 0; j < 3; j++)
{
/* spread the mins / maxs by a pixel */
out->mins[j] = LittleFloat(in->mins[j]) - 1;
out->maxs[j] = LittleFloat(in->maxs[j]) + 1;
out->origin[j] = LittleFloat(in->origin[j]);
}
out->radius = Mod_RadiusFromBounds(out->mins, out->maxs);
out->firstnode = LittleLong(in->headnode);
out->firstmodelsurface = LittleLong(in->firstface);
out->nummodelsurfaces = LittleLong(in->numfaces);
// visleafs
out->numleafs = 0;
// check limits
if (out->firstnode >= loadmodel->numnodes)
{
ri.Sys_Error(ERR_DROP, "%s: Inline model %i has bad firstnode",
__func__, i);
}
}
}
/*
* Fills in s->texturemins[] and s->extents[]
*/
static void
Mod_CalcSurfaceExtents(gl4model_t *loadmodel, msurface_t *s)
{
float mins[2], maxs[2], val;
int i, j, e;
mvertex_t *v;
mtexinfo_t *tex;
int bmins[2], bmaxs[2];
mins[0] = mins[1] = 999999;
maxs[0] = maxs[1] = -99999;
tex = s->texinfo;
for (i = 0; i < s->numedges; i++)
{
e = loadmodel->surfedges[s->firstedge + i];
if (e >= 0)
{
v = &loadmodel->vertexes[loadmodel->edges[e].v[0]];
}
else
{
v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]];
}
for (j = 0; j < 2; j++)
{
val = v->position[0] * tex->vecs[j][0] +
v->position[1] * tex->vecs[j][1] +
v->position[2] * tex->vecs[j][2] +
tex->vecs[j][3];
if (val < mins[j])
{
mins[j] = val;
}
if (val > maxs[j])
{
maxs[j] = val;
}
}
}
for (i = 0; i < 2; i++)
{
bmins[i] = floor(mins[i] / 16);
bmaxs[i] = ceil(maxs[i] / 16);
s->texturemins[i] = bmins[i] * 16;
s->extents[i] = (bmaxs[i] - bmins[i]) * 16;
}
}
extern void
GL4_SubdivideSurface(msurface_t *fa, gl4model_t* loadmodel);
static int calcTexinfoAndFacesSize(byte *mod_base, const lump_t *fl, const lump_t *tl)
{
dface_t* face_in = (void *)(mod_base + fl->fileofs);
texinfo_t* texinfo_in = (void *)(mod_base + tl->fileofs);
if (fl->filelen % sizeof(*face_in) || tl->filelen % sizeof(*texinfo_in))
{
// will error out when actually loading it
return 0;
}
int ret = 0;
int face_count = fl->filelen / sizeof(*face_in);
int texinfo_count = tl->filelen / sizeof(*texinfo_in);
{
// out = Hunk_Alloc(count * sizeof(*out));
int baseSize = face_count * sizeof(msurface_t);
baseSize = (baseSize + 31) & ~31;
ret += baseSize;
int ti_size = texinfo_count * sizeof(mtexinfo_t);
ti_size = (ti_size + 31) & ~31;
ret += ti_size;
}
int numWarpFaces = 0;
for (int surfnum = 0; surfnum < face_count; surfnum++, face_in++)
{
int numverts = LittleShort(face_in->numedges);
int ti = LittleShort(face_in->texinfo);
if ((ti < 0) || (ti >= texinfo_count))
{
return 0; // will error out
}
int texFlags = LittleLong(texinfo_in[ti].flags);
/* set the drawing flags */
if (texFlags & SURF_WARP)
{
if (numverts > 60)
return 0; // will error out in R_SubdividePolygon()
// GL4_SubdivideSurface(out, loadmodel); /* cut up polygon for warps */
// for each (pot. recursive) call to R_SubdividePolygon():
// sizeof(glpoly_t) + ((numverts - 4) + 2) * sizeof(gl4_3D_vtx_t)
// this is tricky, how much is allocated depends on the size of the surface
// which we don't know (we'd need the vertices etc to know, but we can't load
// those without allocating...)
// so we just count warped faces and use a generous estimate below
++numWarpFaces;
}
else
{
// GL4_LM_BuildPolygonFromSurface(out);
// => poly = Hunk_Alloc(sizeof(glpoly_t) + (numverts - 4) * sizeof(gl4_3D_vtx_t));
int polySize = sizeof(glpoly_t) + (numverts - 4) * sizeof(gl4_3D_vtx_t);
polySize = (polySize + 31) & ~31;
ret += polySize;
}
}
// yeah, this is a bit hacky, but it looks like for each warped face
// 256-55000 bytes are allocated (usually on the lower end),
// so just assume 48k per face to be safe
ret += numWarpFaces * 49152;
ret += 5000000; // and 5MB extra just in case
return ret;
}
static void
Mod_LoadFaces(gl4model_t *loadmodel, byte *mod_base, lump_t *l)
{
dface_t *in;
msurface_t *out;
int i, count, surfnum;
int planenum, side;
int ti;
in = (void *)(mod_base + l->fileofs);
if (l->filelen % sizeof(*in))
{
ri.Sys_Error(ERR_DROP, "%s: funny lump size in %s",
__func__, loadmodel->name);
}
count = l->filelen / sizeof(*in);
out = Hunk_Alloc(count * sizeof(*out));
loadmodel->surfaces = out;
loadmodel->numsurfaces = count;
GL4_LM_BeginBuildingLightmaps(loadmodel);
for (surfnum = 0; surfnum < count; surfnum++, in++, out++)
{
out->firstedge = LittleLong(in->firstedge);
out->numedges = LittleShort(in->numedges);
out->flags = 0;
out->polys = NULL;
planenum = LittleShort(in->planenum);
side = LittleShort(in->side);
if (side)
{
out->flags |= SURF_PLANEBACK;
}
if (planenum < 0 || planenum >= loadmodel->numplanes)
{
ri.Sys_Error(ERR_DROP, "%s: Incorrect %d planenum.",
__func__, planenum);
}
out->plane = loadmodel->planes + planenum;
ti = LittleShort(in->texinfo);
if ((ti < 0) || (ti >= loadmodel->numtexinfo))
{
ri.Sys_Error(ERR_DROP, "%s: bad texinfo number",
__func__);
}
out->texinfo = loadmodel->texinfo + ti;
Mod_CalcSurfaceExtents(loadmodel, out);
/* lighting info */
for (i = 0; i < MAX_LIGHTMAPS_PER_SURFACE; i++)
{
out->styles[i] = in->styles[i];
}
i = LittleLong(in->lightofs);
if (i == -1)
{
out->samples = NULL;
}
else
{
out->samples = loadmodel->lightdata + i;
}
/* set the drawing flags */
if (out->texinfo->flags & SURF_WARP)
{
out->flags |= SURF_DRAWTURB;
for (i = 0; i < 2; i++)
{
out->extents[i] = 16384;
out->texturemins[i] = -8192;
}
GL4_SubdivideSurface(out, loadmodel); /* cut up polygon for warps */
}
if (r_fixsurfsky->value)
{
if (out->texinfo->flags & SURF_SKY)
{
out->flags |= SURF_DRAWSKY;
}
}
/* create lightmaps and polygons */
if (!(out->texinfo->flags & (SURF_SKY | SURF_TRANS33 | SURF_TRANS66 | SURF_WARP)))
{
GL4_LM_CreateSurfaceLightmap(out);
}
if (!(out->texinfo->flags & SURF_WARP))
{
GL4_LM_BuildPolygonFromSurface(loadmodel, out);
}
}
GL4_LM_EndBuildingLightmaps();
}
static void
Mod_LoadLeafs(gl4model_t *loadmodel, byte *mod_base, lump_t *l)
{
dleaf_t *in;
mleaf_t *out;
int i, j, count, p;
in = (void *)(mod_base + l->fileofs);
if (l->filelen % sizeof(*in))
{
ri.Sys_Error(ERR_DROP, "%s: funny lump size in %s",
__func__, loadmodel->name);
}
count = l->filelen / sizeof(*in);
out = Hunk_Alloc(count * sizeof(*out));
loadmodel->leafs = out;
loadmodel->numleafs = count;
for (i = 0; i < count; i++, in++, out++)
{
unsigned firstleafface;
for (j = 0; j < 3; j++)
{
out->minmaxs[j] = LittleShort(in->mins[j]);
out->minmaxs[3 + j] = LittleShort(in->maxs[j]);
}
p = LittleLong(in->contents);
out->contents = p;
out->cluster = LittleShort(in->cluster);
out->area = LittleShort(in->area);
// make unsigned long from signed short
firstleafface = LittleShort(in->firstleafface) & 0xFFFF;
out->nummarksurfaces = LittleShort(in->numleaffaces) & 0xFFFF;
out->firstmarksurface = loadmodel->marksurfaces + firstleafface;
if ((firstleafface + out->nummarksurfaces) > loadmodel->nummarksurfaces)
{
ri.Sys_Error(ERR_DROP, "%s: wrong marksurfaces position in %s",
__func__, loadmodel->name);
}
}
}
static void
Mod_LoadMarksurfaces(gl4model_t *loadmodel, byte *mod_base, lump_t *l)
{
int i, j, count;
short *in;
msurface_t **out;
in = (void *)(mod_base + l->fileofs);
if (l->filelen % sizeof(*in))
{
ri.Sys_Error(ERR_DROP, "%s: funny lump size in %s",
__func__, loadmodel->name);
}
count = l->filelen / sizeof(*in);
out = Hunk_Alloc(count * sizeof(*out));
loadmodel->marksurfaces = out;
loadmodel->nummarksurfaces = count;
for (i = 0; i < count; i++)
{
j = LittleShort(in[i]);
if ((j < 0) || (j >= loadmodel->numsurfaces))
{
ri.Sys_Error(ERR_DROP, "%s: bad surface number", __func__);
}
out[i] = loadmodel->surfaces + j;
}
}
static void
Mod_LoadBrushModel(gl4model_t *mod, void *buffer, int modfilelen)
{
int i;
dheader_t *header;
byte *mod_base;
if (mod != mod_known)
{
ri.Sys_Error(ERR_DROP, "Loaded a brush model after the world");
}
header = (dheader_t *)buffer;
i = LittleLong(header->version);
if (i != BSPVERSION)
{
ri.Sys_Error(ERR_DROP, "%s: %s has wrong version number (%i should be %i)",
__func__, mod->name, i, BSPVERSION);
}
/* swap all the lumps */
mod_base = (byte *)header;
for (i = 0; i < sizeof(dheader_t) / 4; i++)
{
((int *)header)[i] = LittleLong(((int *)header)[i]);
}
// calculate the needed hunksize from the lumps
int hunkSize = 0;
hunkSize += Mod_CalcLumpHunkSize(&header->lumps[LUMP_VERTEXES], sizeof(dvertex_t), sizeof(mvertex_t), 0);
hunkSize += Mod_CalcLumpHunkSize(&header->lumps[LUMP_EDGES], sizeof(dedge_t), sizeof(medge_t), 0);
hunkSize += sizeof(medge_t) + 31; // for count+1 in Mod_LoadEdges()
int surfEdgeCount = (header->lumps[LUMP_SURFEDGES].filelen+sizeof(int)-1)/sizeof(int);
if(surfEdgeCount < MAX_MAP_SURFEDGES) // else it errors out later anyway
hunkSize += Mod_CalcLumpHunkSize(&header->lumps[LUMP_SURFEDGES], sizeof(int), sizeof(int), 0);
hunkSize += Mod_CalcLumpHunkSize(&header->lumps[LUMP_LIGHTING], 1, 1, 0);
hunkSize += Mod_CalcLumpHunkSize(&header->lumps[LUMP_PLANES], sizeof(dplane_t), sizeof(cplane_t)*2, 0);
hunkSize += calcTexinfoAndFacesSize(mod_base, &header->lumps[LUMP_FACES], &header->lumps[LUMP_TEXINFO]);
hunkSize += Mod_CalcLumpHunkSize(&header->lumps[LUMP_LEAFFACES], sizeof(short), sizeof(msurface_t *), 0); // yes, out is indeed a pointer!
hunkSize += Mod_CalcLumpHunkSize(&header->lumps[LUMP_VISIBILITY], 1, 1, 0);
hunkSize += Mod_CalcLumpHunkSize(&header->lumps[LUMP_LEAFS], sizeof(dleaf_t), sizeof(mleaf_t), 0);
hunkSize += Mod_CalcLumpHunkSize(&header->lumps[LUMP_NODES], sizeof(dnode_t), sizeof(mnode_t), 0);
hunkSize += Mod_CalcLumpHunkSize(&header->lumps[LUMP_MODELS], sizeof(dmodel_t), sizeof(gl4model_t), 0);
mod->extradata = Hunk_Begin(hunkSize);
mod->type = mod_brush;
/* load into heap */
Mod_LoadVertexes(mod->name, &mod->vertexes, &mod->numvertexes, mod_base,
&header->lumps[LUMP_VERTEXES], 0);
Mod_LoadEdges(mod->name, &mod->edges, &mod->numedges,
mod_base, &header->lumps[LUMP_EDGES], 1);
Mod_LoadSurfedges(mod->name, &mod->surfedges, &mod->numsurfedges,
mod_base, &header->lumps[LUMP_SURFEDGES], 0);
Mod_LoadLighting(&mod->lightdata, mod_base, &header->lumps[LUMP_LIGHTING]);
Mod_LoadPlanes (mod->name, &mod->planes, &mod->numplanes,
mod_base, &header->lumps[LUMP_PLANES], 0);
Mod_LoadTexinfo (mod->name, &mod->texinfo, &mod->numtexinfo,
mod_base, &header->lumps[LUMP_TEXINFO], (findimage_t)GL4_FindImage,
gl4_notexture, 0);
Mod_LoadFaces(mod, mod_base, &header->lumps[LUMP_FACES]);
Mod_LoadMarksurfaces(mod, mod_base, &header->lumps[LUMP_LEAFFACES]);
Mod_LoadVisibility(&mod->vis, mod_base, &header->lumps[LUMP_VISIBILITY]);
Mod_LoadLeafs(mod, mod_base, &header->lumps[LUMP_LEAFS]);
Mod_LoadNodes(mod->name, mod->planes, mod->numplanes, mod->leafs,
mod->numleafs, &mod->nodes, &mod->numnodes, mod_base,
&header->lumps[LUMP_NODES]);
Mod_LoadSubmodels (mod, mod_base, &header->lumps[LUMP_MODELS]);
mod->numframes = 2; /* regular and alternate animation */
}
static void
Mod_Free(gl4model_t *mod)
{
Hunk_Free(mod->extradata);
memset(mod, 0, sizeof(*mod));
}
void
GL4_Mod_FreeAll(void)
{
int i;
for (i = 0; i < mod_numknown; i++)
{
if (mod_known[i].extradatasize)
{
Mod_Free(&mod_known[i]);
}
}
}
/*
* Loads in a model for the given name
*/
static gl4model_t *
Mod_ForName (char *name, gl4model_t *parent_model, qboolean crash)
{
gl4model_t *mod;
void *buf;
int i, modfilelen;
if (!name[0])
{
ri.Sys_Error(ERR_DROP, "%s: NULL name", __func__);
}
/* inline models are grabbed only from worldmodel */
if (name[0] == '*' && parent_model)
{
i = (int)strtol(name + 1, (char **)NULL, 10);
if (i < 1 || i >= parent_model->numsubmodels)
{
ri.Sys_Error(ERR_DROP, "%s: bad inline model number",
__func__);
}
return &parent_model->submodels[i];
}
/* search the currently loaded models */
for (i = 0, mod = mod_known; i < mod_numknown; i++, mod++)
{
if (!mod->name[0])
{
continue;
}
if (!strcmp(mod->name, name))
{
return mod;
}
}
/* find a free model slot spot */
for (i = 0, mod = mod_known; i < mod_numknown; i++, mod++)
{
if (!mod->name[0])
{
break; /* free spot */
}
}
if (i == mod_numknown)
{
if (mod_numknown == MAX_MOD_KNOWN)
{
ri.Sys_Error(ERR_DROP, "mod_numknown == MAX_MOD_KNOWN");
}
mod_numknown++;
}
strcpy(mod->name, name);
/* load the file */
modfilelen = ri.FS_LoadFile(mod->name, (void **)&buf);
if (!buf)
{
if (crash)
{
ri.Sys_Error(ERR_DROP, "%s: %s not found",
__func__, mod->name);
}
memset(mod->name, 0, sizeof(mod->name));
return NULL;
}
/* call the apropriate loader */
switch (LittleLong(*(unsigned *)buf))
{
case DKMHEADER:
/* fall through */
case RAVENFMHEADER:
/* fall through */
case IDALIASHEADER:
/* fall through */
case IDMDLHEADER:
{
mod->extradata = Mod_LoadAliasModel(mod->name, buf, modfilelen,
mod->mins, mod->maxs,
(struct image_s **)mod->skins, (findimage_t)GL4_FindImage,
&(mod->type));
if (!mod->extradata)
{
ri.Sys_Error(ERR_DROP, "%s: Failed to load %s",
__func__, mod->name);
}
};
break;
case IDSPRITEHEADER:
{
mod->extradata = Mod_LoadSP2(mod->name, buf, modfilelen,
(struct image_s **)mod->skins, (findimage_t)GL4_FindImage,
&(mod->type));
if (!mod->extradata)
{
ri.Sys_Error(ERR_DROP, "%s: Failed to load %s",
__func__, mod->name);
}
}
break;
case IDBSPHEADER:
Mod_LoadBrushModel(mod, buf, modfilelen);
break;
default:
ri.Sys_Error(ERR_DROP, "%s: unknown fileid for %s",
__func__, mod->name);
break;
}
mod->extradatasize = Hunk_End();
ri.FS_FreeFile(buf);
return mod;
}
/*
* Specifies the model that will be used as the world
*/
void
GL4_BeginRegistration(char *model)
{
char fullname[MAX_QPATH];
cvar_t *flushmap;
registration_sequence++;
gl4_oldviewcluster = -1; /* force markleafs */
gl4state.currentlightmap = -1;
Com_sprintf(fullname, sizeof(fullname), "maps/%s.bsp", model);
/* explicitly free the old map if different
this guarantees that mod_known[0] is the
world map */
flushmap = ri.Cvar_Get("flushmap", "0", 0);
if (strcmp(mod_known[0].name, fullname) || flushmap->value)
{
Mod_Free(&mod_known[0]);
}
gl4_worldmodel = Mod_ForName(fullname, NULL, true);
gl4_viewcluster = -1;
}
struct model_s *
GL4_RegisterModel(char *name)
{
gl4model_t *mod;
mod = Mod_ForName(name, gl4_worldmodel, false);
if (mod)
{
mod->registration_sequence = registration_sequence;
/* register any images used by the models */
if (mod->type == mod_brush)
{
int i;
for (i = 0; i < mod->numtexinfo; i++)
{
mod->texinfo[i].image->registration_sequence = registration_sequence;
}
}
else
{
/* numframes is unused for SP2 but lets set it also */
mod->numframes = Mod_ReLoadSkins((struct image_s **)mod->skins,
(findimage_t)GL4_FindImage, mod->extradata, mod->type);
}
}
return mod;
}
void
GL4_EndRegistration(void)
{
int i;
gl4model_t *mod;
if (Mod_HasFreeSpace() && GL4_ImageHasFreeSpace())
{
// should be enough space for load next maps
return;
}
for (i = 0, mod = mod_known; i < mod_numknown; i++, mod++)
{
if (!mod->name[0])
{
continue;
}
if (mod->registration_sequence != registration_sequence)
{
/* don't need this model */
Mod_Free(mod);
}
}
GL4_FreeUnusedImages();
}

View file

@ -0,0 +1,436 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (C) 2016-2017 Daniel Gibson
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* SDL backend for the GL4 renderer. Everything that needs to be on the
* renderer side of thing. Also all glad (or whatever OpenGL loader I
* end up using) specific things.
*
* =======================================================================
*/
#include "header/local.h"
#include <SDL2/SDL.h>
static SDL_Window* window = NULL;
static SDL_GLContext context = NULL;
static qboolean vsyncActive = false;
qboolean IsHighDPIaware = false;
// --------
enum {
// Not all GL.h header know about GL_DEBUG_SEVERITY_NOTIFICATION_*.
// DG: yes, it's the same value in GLES3.2
QGL_DEBUG_SEVERITY_NOTIFICATION = 0x826B
};
/*
* Callback function for debug output.
*/
static void APIENTRY
DebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
const GLchar *message, const void *userParam)
{
const char* sourceStr = "Source: Unknown";
const char* typeStr = "Type: Unknown";
const char* severityStr = "Severity: Unknown";
switch (severity)
{
#ifdef YQ2_GL3_GLES
#define SVRCASE(X, STR) case GL_DEBUG_SEVERITY_ ## X ## _KHR : severityStr = STR; break;
#else // Desktop GL
#define SVRCASE(X, STR) case GL_DEBUG_SEVERITY_ ## X ## _ARB : severityStr = STR; break;
#endif
case QGL_DEBUG_SEVERITY_NOTIFICATION: return;
SVRCASE(HIGH, "Severity: High")
SVRCASE(MEDIUM, "Severity: Medium")
SVRCASE(LOW, "Severity: Low")
#undef SVRCASE
}
switch (source)
{
#ifdef YQ2_GL3_GLES
#define SRCCASE(X) case GL_DEBUG_SOURCE_ ## X ## _KHR: sourceStr = "Source: " #X; break;
#else
#define SRCCASE(X) case GL_DEBUG_SOURCE_ ## X ## _ARB: sourceStr = "Source: " #X; break;
#endif
SRCCASE(API);
SRCCASE(WINDOW_SYSTEM);
SRCCASE(SHADER_COMPILER);
SRCCASE(THIRD_PARTY);
SRCCASE(APPLICATION);
SRCCASE(OTHER);
#undef SRCCASE
}
switch(type)
{
#ifdef YQ2_GL3_GLES
#define TYPECASE(X) case GL_DEBUG_TYPE_ ## X ## _KHR: typeStr = "Type: " #X; break;
#else
#define TYPECASE(X) case GL_DEBUG_TYPE_ ## X ## _ARB: typeStr = "Type: " #X; break;
#endif
TYPECASE(ERROR);
TYPECASE(DEPRECATED_BEHAVIOR);
TYPECASE(UNDEFINED_BEHAVIOR);
TYPECASE(PORTABILITY);
TYPECASE(PERFORMANCE);
TYPECASE(OTHER);
#undef TYPECASE
}
// use PRINT_ALL - this is only called with gl4_debugcontext != 0 anyway.
R_Printf(PRINT_ALL, "GLDBG %s %s %s: %s\n", sourceStr, typeStr, severityStr, message);
}
// ---------
/*
* Swaps the buffers and shows the next frame.
*/
void GL4_EndFrame(void)
{
if(gl4config.useBigVBO)
{
// I think this is a good point to orphan the VBO and get a fresh one
GL4_BindVAO(gl4state.vao3D);
GL4_BindVBO(gl4state.vbo3D);
glBufferData(GL_ARRAY_BUFFER, gl4state.vbo3Dsize, NULL, GL_STREAM_DRAW);
gl4state.vbo3DcurOffset = 0;
}
SDL_GL_SwapWindow(window);
}
/*
* Returns whether the vsync is enabled.
*/
qboolean GL4_IsVsyncActive(void)
{
return vsyncActive;
}
/*
* Enables or disabes the vsync.
*/
void GL4_SetVsync(void)
{
// Make sure that the user given
// value is SDL compatible...
int vsync = 0;
if (r_vsync->value == 1)
{
vsync = 1;
}
else if (r_vsync->value == 2)
{
vsync = -1;
}
if (SDL_GL_SetSwapInterval(vsync) == -1)
{
if (vsync == -1)
{
// Not every system supports adaptive
// vsync, fallback to normal vsync.
R_Printf(PRINT_ALL, "Failed to set adaptive vsync, reverting to normal vsync.\n");
SDL_GL_SetSwapInterval(1);
}
}
vsyncActive = SDL_GL_GetSwapInterval() != 0;
}
/*
* This function returns the flags used at the SDL window
* creation by GLimp_InitGraphics(). In case of error -1
* is returned.
*/
int GL4_PrepareForWindow(void)
{
// Mkay, let's try to load the libGL,
const char *libgl;
cvar_t *gl4_libgl = ri.Cvar_Get("gl4_libgl", "", CVAR_ARCHIVE);
if (strlen(gl4_libgl->string) == 0)
{
libgl = NULL;
}
else
{
libgl = gl4_libgl->string;
}
while (1)
{
if (SDL_GL_LoadLibrary(libgl) < 0)
{
if (libgl == NULL)
{
ri.Sys_Error(ERR_FATAL, "%s: Couldn't load libGL: %s!",
__func__, SDL_GetError());
return -1;
}
else
{
R_Printf(PRINT_ALL, "%s: Couldn't load libGL: %s!\n",
__func__, SDL_GetError());
R_Printf(PRINT_ALL, "Retrying with default...\n");
ri.Cvar_Set("gl3_libgl", "");
libgl = NULL;
}
}
else
{
break;
}
}
// Set GL context attributs bound to the window.
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
if (SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8) == 0)
{
gl4config.stencil = true;
}
else
{
gl4config.stencil = false;
}
#ifdef YQ2_GL3_GLES3
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
#else // Desktop GL
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
#endif
// Set GL context flags.
int contextFlags = 0;
#ifndef YQ2_GL3_GLES // Desktop GL (at least RPi4 doesn't like this for GLES3)
contextFlags |= SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG;
#endif
if (gl4_debugcontext && gl4_debugcontext->value)
{
contextFlags |= SDL_GL_CONTEXT_DEBUG_FLAG;
}
if (contextFlags != 0)
{
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, contextFlags);
}
// Let's see if the driver supports MSAA.
int msaa_samples = 0;
if (gl_msaa_samples->value)
{
msaa_samples = gl_msaa_samples->value;
if (SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1) < 0)
{
R_Printf(PRINT_ALL, "MSAA is unsupported: %s\n", SDL_GetError());
ri.Cvar_SetValue ("r_msaa_samples", 0);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
}
else if (SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, msaa_samples) < 0)
{
R_Printf(PRINT_ALL, "MSAA %ix is unsupported: %s\n", msaa_samples, SDL_GetError());
ri.Cvar_SetValue("r_msaa_samples", 0);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
}
}
else
{
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
}
return SDL_WINDOW_OPENGL;
}
/*
* Initializes the OpenGL context. Returns true at
* success and false at failure.
*/
int GL4_InitContext(void* win)
{
// Coders are stupid.
if (win == NULL)
{
ri.Sys_Error(ERR_FATAL, "R_InitContext() must not be called with NULL argument!");
return false;
}
window = (SDL_Window *)win;
// Initialize GL context.
context = SDL_GL_CreateContext(window);
if(context == NULL)
{
R_Printf(PRINT_ALL, "GL4_InitContext(): Creating OpenGL Context failed: %s\n", SDL_GetError());
window = NULL;
return false;
}
// Check if we've got the requested MSAA.
int msaa_samples = 0;
if (gl_msaa_samples->value)
{
if (SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &msaa_samples) == 0)
{
ri.Cvar_SetValue("r_msaa_samples", msaa_samples);
}
}
// Check if we've got at least 8 stencil bits
int stencil_bits = 0;
if (gl4config.stencil)
{
if (SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &stencil_bits) < 0 || stencil_bits < 8)
{
gl4config.stencil = false;
}
}
// Enable vsync if requested.
GL4_SetVsync();
// Load GL pointrs through GLAD and check context.
#ifdef YQ2_GL3_GLES
if( !gladLoadGLES2Loader(SDL_GL_GetProcAddress))
#else // Desktop GL
if( !gladLoadGLLoader(SDL_GL_GetProcAddress))
#endif
{
R_Printf(PRINT_ALL, "GL4_InitContext(): ERROR: loading OpenGL function pointers failed!\n");
return false;
}
#ifdef YQ2_GL3_GLES3
else if (GLVersion.major < 3)
#else // Desktop GL
else if (GLVersion.major < 4 || (GLVersion.major == 4 && GLVersion.minor < 6))
#endif
{
R_Printf(PRINT_ALL, "GL4_InitContext(): ERROR: glad only got GL version %d.%d!\n", GLVersion.major, GLVersion.minor);
return false;
}
else
{
R_Printf(PRINT_ALL, "Successfully loaded OpenGL function pointers using glad, got version %d.%d!\n", GLVersion.major, GLVersion.minor);
}
#ifdef YQ2_GL3_GLES
gl4config.debug_output = GLAD_GL_KHR_debug != 0;
#else // Desktop GL
gl4config.debug_output = GLAD_GL_ARB_debug_output != 0;
#endif
gl4config.anisotropic = GLAD_GL_ARB_texture_filter_anisotropic != 0;
gl4config.major_version = GLVersion.major;
gl4config.minor_version = GLVersion.minor;
// Debug context setup.
if (gl4_debugcontext && gl4_debugcontext->value && gl4config.debug_output)
{
#ifdef YQ2_GL3_GLES
glDebugMessageCallbackKHR(DebugCallback, NULL);
// Call GL3_DebugCallback() synchronously, i.e. directly when and
// where the error happens (so we can get the cause in a backtrace)
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);
#else // Desktop GL
glDebugMessageCallbackARB(DebugCallback, NULL);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
#endif
}
// Window title - set here so we can display renderer name in it.
char title[40] = {0};
#ifdef YQ2_GL3_GLES3
snprintf(title, sizeof(title), "Yamagi Quake II %s - OpenGL ES 3.0", YQ2VERSION);
#else
snprintf(title, sizeof(title), "Yamagi Quake II %s - OpenGL 4.6", YQ2VERSION);
#endif
SDL_SetWindowTitle(window, title);
#if SDL_VERSION_ATLEAST(2, 26, 0)
// Figure out if we are high dpi aware.
int flags = SDL_GetWindowFlags(win);
IsHighDPIaware = (flags & SDL_WINDOW_ALLOW_HIGHDPI) ? true : false;
#endif
return true;
}
/*
* Fills the actual size of the drawable into width and height.
*/
void GL4_GetDrawableSize(int* width, int* height)
{
SDL_GL_GetDrawableSize(window, width, height);
}
/*
* Shuts the GL context down.
*/
void GL4_ShutdownContext()
{
if (window)
{
if(context)
{
SDL_GL_DeleteContext(context);
context = NULL;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,885 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (C) 2016-2017 Daniel Gibson
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Surface generation and drawing
*
* =======================================================================
*/
#include <assert.h>
#include <stddef.h> // ofsetof()
#include "header/local.h"
int c_visible_lightmaps;
int c_visible_textures;
static vec3_t modelorg; /* relative to viewpoint */
static msurface_t *gl4_alpha_surfaces;
gl4lightmapstate_t gl4_lms;
#define BACKFACE_EPSILON 0.01
extern gl4image_t gl4textures[MAX_GL4TEXTURES];
extern int numgl4textures;
void GL4_SurfInit(void)
{
// init the VAO and VBO for the standard vertexdata: 10 floats and 1 uint
// (X, Y, Z), (S, T), (LMS, LMT), (normX, normY, normZ) ; lightFlags - last two groups for lightmap/dynlights
glGenVertexArrays(1, &gl4state.vao3D);
GL4_BindVAO(gl4state.vao3D);
glGenBuffers(1, &gl4state.vbo3D);
GL4_BindVBO(gl4state.vbo3D);
if(gl4config.useBigVBO)
{
gl4state.vbo3Dsize = 5*1024*1024; // a 5MB buffer seems to work well?
gl4state.vbo3DcurOffset = 0;
glBufferData(GL_ARRAY_BUFFER, gl4state.vbo3Dsize, NULL, GL_STREAM_DRAW); // allocate/reserve that data
}
glEnableVertexAttribArray(GL4_ATTRIB_POSITION);
qglVertexAttribPointer(GL4_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof(gl4_3D_vtx_t), 0);
glEnableVertexAttribArray(GL4_ATTRIB_TEXCOORD);
qglVertexAttribPointer(GL4_ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(gl4_3D_vtx_t), offsetof(gl4_3D_vtx_t, texCoord));
glEnableVertexAttribArray(GL4_ATTRIB_LMTEXCOORD);
qglVertexAttribPointer(GL4_ATTRIB_LMTEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(gl4_3D_vtx_t), offsetof(gl4_3D_vtx_t, lmTexCoord));
glEnableVertexAttribArray(GL4_ATTRIB_NORMAL);
qglVertexAttribPointer(GL4_ATTRIB_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof(gl4_3D_vtx_t), offsetof(gl4_3D_vtx_t, normal));
glEnableVertexAttribArray(GL4_ATTRIB_LIGHTFLAGS);
qglVertexAttribIPointer(GL4_ATTRIB_LIGHTFLAGS, 1, GL_UNSIGNED_INT, sizeof(gl4_3D_vtx_t), offsetof(gl4_3D_vtx_t, lightFlags));
// init VAO and VBO for model vertexdata: 9 floats
// (X,Y,Z), (S,T), (R,G,B,A)
glGenVertexArrays(1, &gl4state.vaoAlias);
GL4_BindVAO(gl4state.vaoAlias);
glGenBuffers(1, &gl4state.vboAlias);
GL4_BindVBO(gl4state.vboAlias);
glEnableVertexAttribArray(GL4_ATTRIB_POSITION);
qglVertexAttribPointer(GL4_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), 0);
glEnableVertexAttribArray(GL4_ATTRIB_TEXCOORD);
qglVertexAttribPointer(GL4_ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), 3*sizeof(GLfloat));
glEnableVertexAttribArray(GL4_ATTRIB_COLOR);
qglVertexAttribPointer(GL4_ATTRIB_COLOR, 4, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), 5*sizeof(GLfloat));
glGenBuffers(1, &gl4state.eboAlias);
// init VAO and VBO for particle vertexdata: 9 floats
// (X,Y,Z), (point_size,distace_to_camera), (R,G,B,A)
glGenVertexArrays(1, &gl4state.vaoParticle);
GL4_BindVAO(gl4state.vaoParticle);
glGenBuffers(1, &gl4state.vboParticle);
GL4_BindVBO(gl4state.vboParticle);
glEnableVertexAttribArray(GL4_ATTRIB_POSITION);
qglVertexAttribPointer(GL4_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), 0);
// TODO: maybe move point size and camera origin to UBO and calculate distance in vertex shader
glEnableVertexAttribArray(GL4_ATTRIB_TEXCOORD); // it's abused for (point_size, distance) here..
qglVertexAttribPointer(GL4_ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), 3*sizeof(GLfloat));
glEnableVertexAttribArray(GL4_ATTRIB_COLOR);
qglVertexAttribPointer(GL4_ATTRIB_COLOR, 4, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), 5*sizeof(GLfloat));
}
void GL4_SurfShutdown(void)
{
glDeleteBuffers(1, &gl4state.vbo3D);
gl4state.vbo3D = 0;
glDeleteVertexArrays(1, &gl4state.vao3D);
gl4state.vao3D = 0;
glDeleteBuffers(1, &gl4state.eboAlias);
gl4state.eboAlias = 0;
glDeleteBuffers(1, &gl4state.vboAlias);
gl4state.vboAlias = 0;
glDeleteVertexArrays(1, &gl4state.vaoAlias);
gl4state.vaoAlias = 0;
}
static void
SetLightFlags(msurface_t *surf)
{
unsigned int lightFlags = 0;
if (surf->dlightframe == gl4_framecount)
{
lightFlags = surf->dlightbits;
}
gl4_3D_vtx_t* verts = surf->polys->vertices;
int numVerts = surf->polys->numverts;
for(int i=0; i<numVerts; ++i)
{
verts[i].lightFlags = lightFlags;
}
}
static void
SetAllLightFlags(msurface_t *surf)
{
unsigned int lightFlags = 0xffffffff;
gl4_3D_vtx_t* verts = surf->polys->vertices;
int numVerts = surf->polys->numverts;
for(int i=0; i<numVerts; ++i)
{
verts[i].lightFlags = lightFlags;
}
}
void
GL4_DrawGLPoly(msurface_t *fa)
{
glpoly_t *p = fa->polys;
GL4_BindVAO(gl4state.vao3D);
GL4_BindVBO(gl4state.vbo3D);
GL4_BufferAndDraw3D(p->vertices, p->numverts, GL_TRIANGLE_FAN);
}
void
GL4_DrawGLFlowingPoly(msurface_t *fa)
{
glpoly_t *p;
float scroll;
p = fa->polys;
scroll = -64.0f * ((gl4_newrefdef.time / 40.0f) - (int)(gl4_newrefdef.time / 40.0f));
if (scroll == 0.0f)
{
scroll = -64.0f;
}
if(gl4state.uni3DData.scroll != scroll)
{
gl4state.uni3DData.scroll = scroll;
GL4_UpdateUBO3D();
}
GL4_BindVAO(gl4state.vao3D);
GL4_BindVBO(gl4state.vbo3D);
GL4_BufferAndDraw3D(p->vertices, p->numverts, GL_TRIANGLE_FAN);
}
static void
DrawTriangleOutlines(void)
{
STUB_ONCE("TODO: Implement for gl_showtris support!");
#if 0
int i, j;
glpoly_t *p;
if (!gl_showtris->value)
{
return;
}
glDisable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glColor4f(1, 1, 1, 1);
for (i = 0; i < MAX_LIGHTMAPS; i++)
{
msurface_t *surf;
for (surf = gl4_lms.lightmap_surfaces[i];
surf != 0;
surf = surf->lightmapchain)
{
p = surf->polys;
for ( ; p; p = p->chain)
{
for (j = 2; j < p->numverts; j++)
{
GLfloat vtx[12];
unsigned int k;
for (k=0; k<3; k++)
{
vtx[0+k] = p->vertices [ 0 ][ k ];
vtx[3+k] = p->vertices [ j - 1 ][ k ];
vtx[6+k] = p->vertices [ j ][ k ];
vtx[9+k] = p->vertices [ 0 ][ k ];
}
glEnableClientState( GL_VERTEX_ARRAY );
glVertexPointer( 3, GL_FLOAT, 0, vtx );
glDrawArrays( GL_LINE_STRIP, 0, 4 );
glDisableClientState( GL_VERTEX_ARRAY );
}
}
}
}
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
#endif // 0
}
static void
UpdateLMscales(const hmm_vec4 lmScales[MAX_LIGHTMAPS_PER_SURFACE], gl4ShaderInfo_t* si)
{
int i;
qboolean hasChanged = false;
for(i=0; i<MAX_LIGHTMAPS_PER_SURFACE; ++i)
{
if(hasChanged)
{
si->lmScales[i] = lmScales[i];
}
else if( si->lmScales[i].R != lmScales[i].R
|| si->lmScales[i].G != lmScales[i].G
|| si->lmScales[i].B != lmScales[i].B
|| si->lmScales[i].A != lmScales[i].A )
{
si->lmScales[i] = lmScales[i];
hasChanged = true;
}
}
if(hasChanged)
{
glUniform4fv(si->uniLmScalesOrTime, MAX_LIGHTMAPS_PER_SURFACE, si->lmScales[0].Elements);
}
}
static void
RenderBrushPoly(entity_t *currententity, msurface_t *fa)
{
int map;
gl4image_t *image;
c_brush_polys++;
image = R_TextureAnimation(currententity, fa->texinfo);
if (fa->flags & SURF_DRAWTURB)
{
GL4_Bind(image->texnum);
GL4_EmitWaterPolys(fa);
return;
}
else
{
GL4_Bind(image->texnum);
}
hmm_vec4 lmScales[MAX_LIGHTMAPS_PER_SURFACE] = {0};
lmScales[0] = HMM_Vec4(1.0f, 1.0f, 1.0f, 1.0f);
GL4_BindLightmap(fa->lightmaptexturenum);
// Any dynamic lights on this surface?
for (map = 0; map < MAX_LIGHTMAPS_PER_SURFACE && fa->styles[map] != 255; map++)
{
lmScales[map].R = gl4_newrefdef.lightstyles[fa->styles[map]].rgb[0];
lmScales[map].G = gl4_newrefdef.lightstyles[fa->styles[map]].rgb[1];
lmScales[map].B = gl4_newrefdef.lightstyles[fa->styles[map]].rgb[2];
lmScales[map].A = 1.0f;
}
if (fa->texinfo->flags & SURF_FLOWING)
{
GL4_UseProgram(gl4state.si3DlmFlow.shaderProgram);
UpdateLMscales(lmScales, &gl4state.si3DlmFlow);
GL4_DrawGLFlowingPoly(fa);
}
else
{
GL4_UseProgram(gl4state.si3Dlm.shaderProgram);
UpdateLMscales(lmScales, &gl4state.si3Dlm);
GL4_DrawGLPoly(fa);
}
// Note: lightmap chains are gone, lightmaps are rendered together with normal texture in one pass
}
/*
* Draw water surfaces and windows.
* The BSP tree is waled front to back, so unwinding the chain
* of alpha_surfaces will draw back to front, giving proper ordering.
*/
void
GL4_DrawAlphaSurfaces(void)
{
msurface_t *s;
/* go back to the world matrix */
gl4state.uni3DData.transModelMat4 = gl4_identityMat4;
GL4_UpdateUBO3D();
glEnable(GL_BLEND);
for (s = gl4_alpha_surfaces; s != NULL; s = s->texturechain)
{
GL4_Bind(s->texinfo->image->texnum);
c_brush_polys++;
float alpha = 1.0f;
if (s->texinfo->flags & SURF_TRANS33)
{
alpha = 0.333f;
}
else if (s->texinfo->flags & SURF_TRANS66)
{
alpha = 0.666f;
}
if(alpha != gl4state.uni3DData.alpha)
{
gl4state.uni3DData.alpha = alpha;
GL4_UpdateUBO3D();
}
if (s->flags & SURF_DRAWTURB)
{
GL4_EmitWaterPolys(s);
}
else if (s->texinfo->flags & SURF_FLOWING)
{
GL4_UseProgram(gl4state.si3DtransFlow.shaderProgram);
GL4_DrawGLFlowingPoly(s);
}
else
{
GL4_UseProgram(gl4state.si3Dtrans.shaderProgram);
GL4_DrawGLPoly(s);
}
}
gl4state.uni3DData.alpha = 1.0f;
GL4_UpdateUBO3D();
glDisable(GL_BLEND);
gl4_alpha_surfaces = NULL;
}
static void
DrawTextureChains(entity_t *currententity)
{
int i;
msurface_t *s;
gl4image_t *image;
c_visible_textures = 0;
for (i = 0, image = gl4textures; i < numgl4textures; i++, image++)
{
if (!image->registration_sequence)
{
continue;
}
s = image->texturechain;
if (!s)
{
continue;
}
c_visible_textures++;
for ( ; s; s = s->texturechain)
{
SetLightFlags(s);
RenderBrushPoly(currententity, s);
}
image->texturechain = NULL;
}
// TODO: maybe one loop for normal faces and one for SURF_DRAWTURB ???
}
static void
RenderLightmappedPoly(entity_t *currententity, msurface_t *surf)
{
int map;
gl4image_t *image = R_TextureAnimation(currententity, surf->texinfo);
hmm_vec4 lmScales[MAX_LIGHTMAPS_PER_SURFACE] = {0};
lmScales[0] = HMM_Vec4(1.0f, 1.0f, 1.0f, 1.0f);
assert((surf->texinfo->flags & (SURF_SKY | SURF_TRANS33 | SURF_TRANS66 | SURF_WARP)) == 0
&& "RenderLightMappedPoly mustn't be called with transparent, sky or warping surfaces!");
// Any dynamic lights on this surface?
for (map = 0; map < MAX_LIGHTMAPS_PER_SURFACE && surf->styles[map] != 255; map++)
{
lmScales[map].R = gl4_newrefdef.lightstyles[surf->styles[map]].rgb[0];
lmScales[map].G = gl4_newrefdef.lightstyles[surf->styles[map]].rgb[1];
lmScales[map].B = gl4_newrefdef.lightstyles[surf->styles[map]].rgb[2];
lmScales[map].A = 1.0f;
}
c_brush_polys++;
GL4_Bind(image->texnum);
GL4_BindLightmap(surf->lightmaptexturenum);
if (surf->texinfo->flags & SURF_FLOWING)
{
GL4_UseProgram(gl4state.si3DlmFlow.shaderProgram);
UpdateLMscales(lmScales, &gl4state.si3DlmFlow);
GL4_DrawGLFlowingPoly(surf);
}
else
{
GL4_UseProgram(gl4state.si3Dlm.shaderProgram);
UpdateLMscales(lmScales, &gl4state.si3Dlm);
GL4_DrawGLPoly(surf);
}
}
static void
DrawInlineBModel(entity_t *currententity, gl4model_t *currentmodel)
{
int i, k;
cplane_t *pplane;
float dot;
msurface_t *psurf;
dlight_t *lt;
/* calculate dynamic lighting for bmodel */
lt = gl4_newrefdef.dlights;
for (k = 0; k < gl4_newrefdef.num_dlights; k++, lt++)
{
R_MarkLights(lt, 1 << k, currentmodel->nodes + currentmodel->firstnode,
r_dlightframecount, GL4_MarkSurfaceLights);
}
psurf = &currentmodel->surfaces[currentmodel->firstmodelsurface];
if (currententity->flags & RF_TRANSLUCENT)
{
glEnable(GL_BLEND);
/* TODO: should I care about the 0.25 part? we'll just set alpha to 0.33 or 0.66 depending on surface flag..
glColor4f(1, 1, 1, 0.25);
R_TexEnv(GL_MODULATE);
*/
}
/* draw texture */
for (i = 0; i < currentmodel->nummodelsurfaces; i++, psurf++)
{
/* find which side of the node we are on */
pplane = psurf->plane;
dot = DotProduct(modelorg, pplane->normal) - pplane->dist;
/* draw the polygon */
if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) ||
(!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON)))
{
if (psurf->texinfo->flags & (SURF_TRANS33 | SURF_TRANS66))
{
/* add to the translucent chain */
psurf->texturechain = gl4_alpha_surfaces;
gl4_alpha_surfaces = psurf;
}
else if(!(psurf->flags & SURF_DRAWTURB))
{
SetAllLightFlags(psurf);
RenderLightmappedPoly(currententity, psurf);
}
else
{
RenderBrushPoly(currententity, psurf);
}
}
}
if (currententity->flags & RF_TRANSLUCENT)
{
glDisable(GL_BLEND);
}
}
void
GL4_DrawBrushModel(entity_t *e, gl4model_t *currentmodel)
{
vec3_t mins, maxs;
int i;
qboolean rotated;
if (currentmodel->nummodelsurfaces == 0)
{
return;
}
gl4state.currenttexture = -1;
if (e->angles[0] || e->angles[1] || e->angles[2])
{
rotated = true;
for (i = 0; i < 3; i++)
{
mins[i] = e->origin[i] - currentmodel->radius;
maxs[i] = e->origin[i] + currentmodel->radius;
}
}
else
{
rotated = false;
VectorAdd(e->origin, currentmodel->mins, mins);
VectorAdd(e->origin, currentmodel->maxs, maxs);
}
if (r_cull->value && R_CullBox(mins, maxs, frustum))
{
return;
}
if (gl_zfix->value)
{
glEnable(GL_POLYGON_OFFSET_FILL);
}
VectorSubtract(gl4_newrefdef.vieworg, e->origin, modelorg);
if (rotated)
{
vec3_t temp;
vec3_t forward, right, up;
VectorCopy(modelorg, temp);
AngleVectors(e->angles, forward, right, up);
modelorg[0] = DotProduct(temp, forward);
modelorg[1] = -DotProduct(temp, right);
modelorg[2] = DotProduct(temp, up);
}
//glPushMatrix();
hmm_mat4 oldMat = gl4state.uni3DData.transModelMat4;
e->angles[0] = -e->angles[0];
e->angles[2] = -e->angles[2];
GL4_RotateForEntity(e);
e->angles[0] = -e->angles[0];
e->angles[2] = -e->angles[2];
DrawInlineBModel(e, currentmodel);
// glPopMatrix();
gl4state.uni3DData.transModelMat4 = oldMat;
GL4_UpdateUBO3D();
if (gl_zfix->value)
{
glDisable(GL_POLYGON_OFFSET_FILL);
}
}
static void
RecursiveWorldNode(entity_t *currententity, mnode_t *node)
{
int c, side, sidebit;
cplane_t *plane;
msurface_t *surf, **mark;
mleaf_t *pleaf;
float dot;
gl4image_t *image;
if (node->contents == CONTENTS_SOLID)
{
return; /* solid */
}
if (node->visframe != gl4_visframecount)
{
return;
}
if (r_cull->value && R_CullBox(node->minmaxs, node->minmaxs + 3, frustum))
{
return;
}
/* if a leaf node, draw stuff */
if (node->contents != CONTENTS_NODE)
{
pleaf = (mleaf_t *)node;
/* check for door connected areas */
// check for door connected areas
if (!R_AreaVisible(gl4_newrefdef.areabits, pleaf))
return; // not visible
mark = pleaf->firstmarksurface;
c = pleaf->nummarksurfaces;
if (c)
{
do
{
(*mark)->visframe = gl4_framecount;
mark++;
}
while (--c);
}
return;
}
/* node is just a decision point, so go down the apropriate
sides find which side of the node we are on */
plane = node->plane;
switch (plane->type)
{
case PLANE_X:
dot = modelorg[0] - plane->dist;
break;
case PLANE_Y:
dot = modelorg[1] - plane->dist;
break;
case PLANE_Z:
dot = modelorg[2] - plane->dist;
break;
default:
dot = DotProduct(modelorg, plane->normal) - plane->dist;
break;
}
if (dot >= 0)
{
side = 0;
sidebit = 0;
}
else
{
side = 1;
sidebit = SURF_PLANEBACK;
}
/* recurse down the children, front side first */
RecursiveWorldNode(currententity, node->children[side]);
/* draw stuff */
for (c = node->numsurfaces,
surf = gl4_worldmodel->surfaces + node->firstsurface;
c; c--, surf++)
{
if (surf->visframe != gl4_framecount)
{
continue;
}
if ((surf->flags & SURF_PLANEBACK) != sidebit)
{
continue; /* wrong side */
}
if (surf->texinfo->flags & SURF_SKY)
{
/* just adds to visible sky bounds */
GL4_AddSkySurface(surf);
}
else if (surf->texinfo->flags & (SURF_TRANS33 | SURF_TRANS66))
{
/* add to the translucent chain */
surf->texturechain = gl4_alpha_surfaces;
gl4_alpha_surfaces = surf;
gl4_alpha_surfaces->texinfo->image = R_TextureAnimation(currententity, surf->texinfo);
}
else
{
// calling RenderLightmappedPoly() here probably isn't optimal, rendering everything
// through texturechains should be faster, because far less glBindTexture() is needed
// (and it might allow batching the drawcalls of surfaces with the same texture)
#if 0
if(!(surf->flags & SURF_DRAWTURB))
{
RenderLightmappedPoly(surf);
}
else
#endif // 0
{
/* the polygon is visible, so add it to the texture sorted chain */
image = R_TextureAnimation(currententity, surf->texinfo);
surf->texturechain = image->texturechain;
image->texturechain = surf;
}
}
}
/* recurse down the back side */
RecursiveWorldNode(currententity, node->children[!side]);
}
void
GL4_DrawWorld(void)
{
entity_t ent;
if (!r_drawworld->value)
{
return;
}
if (gl4_newrefdef.rdflags & RDF_NOWORLDMODEL)
{
return;
}
VectorCopy(gl4_newrefdef.vieworg, modelorg);
/* auto cycle the world frame for texture animation */
memset(&ent, 0, sizeof(ent));
ent.frame = (int)(gl4_newrefdef.time * 2);
gl4state.currenttexture = -1;
GL4_ClearSkyBox();
RecursiveWorldNode(&ent, gl4_worldmodel->nodes);
DrawTextureChains(&ent);
GL4_DrawSkyBox();
DrawTriangleOutlines();
}
/*
* Mark the leaves and nodes that are
* in the PVS for the current cluster
*/
void
GL4_MarkLeaves(void)
{
const byte *vis;
YQ2_ALIGNAS_TYPE(int) byte fatvis[MAX_MAP_LEAFS / 8];
mnode_t *node;
int i, c;
mleaf_t *leaf;
int cluster;
if ((gl4_oldviewcluster == gl4_viewcluster) &&
(gl4_oldviewcluster2 == gl4_viewcluster2) &&
!r_novis->value &&
(gl4_viewcluster != -1))
{
return;
}
/* development aid to let you run around
and see exactly where the pvs ends */
if (r_lockpvs->value)
{
return;
}
gl4_visframecount++;
gl4_oldviewcluster = gl4_viewcluster;
gl4_oldviewcluster2 = gl4_viewcluster2;
if (r_novis->value || (gl4_viewcluster == -1) || !gl4_worldmodel->vis)
{
/* mark everything */
for (i = 0; i < gl4_worldmodel->numleafs; i++)
{
gl4_worldmodel->leafs[i].visframe = gl4_visframecount;
}
for (i = 0; i < gl4_worldmodel->numnodes; i++)
{
gl4_worldmodel->nodes[i].visframe = gl4_visframecount;
}
return;
}
vis = GL4_Mod_ClusterPVS(gl4_viewcluster, gl4_worldmodel);
/* may have to combine two clusters because of solid water boundaries */
if (gl4_viewcluster2 != gl4_viewcluster)
{
memcpy(fatvis, vis, (gl4_worldmodel->numleafs + 7) / 8);
vis = GL4_Mod_ClusterPVS(gl4_viewcluster2, gl4_worldmodel);
c = (gl4_worldmodel->numleafs + 31) / 32;
for (i = 0; i < c; i++)
{
((int *)fatvis)[i] |= ((int *)vis)[i];
}
vis = fatvis;
}
for (i = 0, leaf = gl4_worldmodel->leafs;
i < gl4_worldmodel->numleafs;
i++, leaf++)
{
cluster = leaf->cluster;
if (cluster == -1)
{
continue;
}
if (vis[cluster >> 3] & (1 << (cluster & 7)))
{
node = (mnode_t *)leaf;
do
{
if (node->visframe == gl4_visframecount)
{
break;
}
node->visframe = gl4_visframecount;
node = node->parent;
}
while (node);
}
}
}

View file

@ -0,0 +1,754 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (C) 2016-2017 Daniel Gibson
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Warps. Used on water surfaces und for skybox rotation.
*
* =======================================================================
*/
#include "header/local.h"
static void
R_BoundPoly(int numverts, float *verts, vec3_t mins, vec3_t maxs)
{
int i, j;
float *v;
mins[0] = mins[1] = mins[2] = 9999;
maxs[0] = maxs[1] = maxs[2] = -9999;
v = verts;
for (i = 0; i < numverts; i++)
{
for (j = 0; j < 3; j++, v++)
{
if (*v < mins[j])
{
mins[j] = *v;
}
if (*v > maxs[j])
{
maxs[j] = *v;
}
}
}
}
static const float SUBDIVIDE_SIZE = 64.0f;
static void
R_SubdividePolygon(int numverts, float *verts, msurface_t *warpface)
{
int i, j, k;
vec3_t mins, maxs;
float m;
float *v;
vec3_t front[64], back[64];
int f, b;
float dist[64];
float frac;
glpoly_t *poly;
float s, t;
vec3_t total;
float total_s, total_t;
vec3_t normal;
VectorCopy(warpface->plane->normal, normal);
if (numverts > 60)
{
ri.Sys_Error(ERR_DROP, "numverts = %i", numverts);
}
R_BoundPoly(numverts, verts, mins, maxs);
for (i = 0; i < 3; i++)
{
m = (mins[i] + maxs[i]) * 0.5;
m = SUBDIVIDE_SIZE * floor(m / SUBDIVIDE_SIZE + 0.5);
if (maxs[i] - m < 8)
{
continue;
}
if (m - mins[i] < 8)
{
continue;
}
/* cut it */
v = verts + i;
for (j = 0; j < numverts; j++, v += 3)
{
dist[j] = *v - m;
}
/* wrap cases */
dist[j] = dist[0];
v -= i;
VectorCopy(verts, v);
f = b = 0;
v = verts;
for (j = 0; j < numverts; j++, v += 3)
{
if (dist[j] >= 0)
{
VectorCopy(v, front[f]);
f++;
}
if (dist[j] <= 0)
{
VectorCopy(v, back[b]);
b++;
}
if ((dist[j] == 0) || (dist[j + 1] == 0))
{
continue;
}
if ((dist[j] > 0) != (dist[j + 1] > 0))
{
/* clip point */
frac = dist[j] / (dist[j] - dist[j + 1]);
for (k = 0; k < 3; k++)
{
front[f][k] = back[b][k] = v[k] + frac * (v[3 + k] - v[k]);
}
f++;
b++;
}
}
R_SubdividePolygon(f, front[0], warpface);
R_SubdividePolygon(b, back[0], warpface);
return;
}
/* add a point in the center to help keep warp valid */
poly = Hunk_Alloc(sizeof(glpoly_t) + ((numverts - 4) + 2) * sizeof(gl4_3D_vtx_t));
poly->next = warpface->polys;
warpface->polys = poly;
poly->numverts = numverts + 2;
VectorClear(total);
total_s = 0;
total_t = 0;
for (i = 0; i < numverts; i++, verts += 3)
{
VectorCopy(verts, poly->vertices[i + 1].pos);
s = DotProduct(verts, warpface->texinfo->vecs[0]);
t = DotProduct(verts, warpface->texinfo->vecs[1]);
total_s += s;
total_t += t;
VectorAdd(total, verts, total);
poly->vertices[i + 1].texCoord[0] = s;
poly->vertices[i + 1].texCoord[1] = t;
VectorCopy(normal, poly->vertices[i + 1].normal);
poly->vertices[i + 1].lightFlags = 0;
}
VectorScale(total, (1.0 / numverts), poly->vertices[0].pos);
poly->vertices[0].texCoord[0] = total_s / numverts;
poly->vertices[0].texCoord[1] = total_t / numverts;
VectorCopy(normal, poly->vertices[0].normal);
/* copy first vertex to last */
//memcpy(poly->vertices[i + 1], poly->vertices[1], sizeof(poly->vertices[0]));
poly->vertices[i + 1] = poly->vertices[1];
}
/*
* Breaks a polygon up along axial 64 unit
* boundaries so that turbulent and sky warps
* can be done reasonably.
*/
void
GL4_SubdivideSurface(msurface_t *fa, gl4model_t* loadmodel)
{
vec3_t verts[64];
int numverts;
int i;
int lindex;
float *vec;
/* convert edges back to a normal polygon */
numverts = 0;
for (i = 0; i < fa->numedges; i++)
{
lindex = loadmodel->surfedges[fa->firstedge + i];
if (lindex > 0)
{
vec = loadmodel->vertexes[loadmodel->edges[lindex].v[0]].position;
}
else
{
vec = loadmodel->vertexes[loadmodel->edges[-lindex].v[1]].position;
}
VectorCopy(vec, verts[numverts]);
numverts++;
}
R_SubdividePolygon(numverts, verts[0], fa);
}
/*
* Does a water warp on the pre-fragmented glpoly_t chain
*/
void
GL4_EmitWaterPolys(msurface_t *fa)
{
glpoly_t *bp;
float scroll = 0.0f;
if (fa->texinfo->flags & SURF_FLOWING)
{
scroll = -64.0f * ((gl4_newrefdef.time * 0.5) - (int)(gl4_newrefdef.time * 0.5));
if (scroll == 0.0f) // this is done in GL4_DrawGLFlowingPoly() TODO: keep?
{
scroll = -64.0f;
}
}
qboolean updateUni3D = false;
if(gl4state.uni3DData.scroll != scroll)
{
gl4state.uni3DData.scroll = scroll;
updateUni3D = true;
}
// these surfaces (mostly water and lava, I think?) don't have a lightmap.
// rendering water at full brightness looks bad (esp. for water in dark environments)
// so default use a factor of 0.5 (ontop of intensity)
// but lava should be bright and glowing, so use full brightness there
float lightScale = fa->texinfo->image->is_lava ? 1.0f : 0.5f;
if(lightScale != gl4state.uni3DData.lightScaleForTurb)
{
gl4state.uni3DData.lightScaleForTurb = lightScale;
updateUni3D = true;
}
if(updateUni3D)
{
GL4_UpdateUBO3D();
}
GL4_UseProgram(gl4state.si3Dturb.shaderProgram);
GL4_BindVAO(gl4state.vao3D);
GL4_BindVBO(gl4state.vbo3D);
for (bp = fa->polys; bp != NULL; bp = bp->next)
{
GL4_BufferAndDraw3D(bp->vertices, bp->numverts, GL_TRIANGLE_FAN);
}
}
// ########### below: Sky-specific stuff ##########
#define ON_EPSILON 0.1 /* point on plane side epsilon */
enum { MAX_CLIP_VERTS = 64 };
static const int skytexorder[6] = {0, 2, 1, 3, 4, 5};
static float skymins[2][6], skymaxs[2][6];
static float sky_min, sky_max;
static float skyrotate;
static int skyautorotate;
static vec3_t skyaxis;
static gl4image_t* sky_images[6];
/* 3dstudio environment map names */
static const char* suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};
vec3_t skyclip[6] = {
{1, 1, 0},
{1, -1, 0},
{0, -1, 1},
{0, 1, 1},
{1, 0, 1},
{-1, 0, 1}
};
int c_sky;
int st_to_vec[6][3] = {
{3, -1, 2},
{-3, 1, 2},
{1, 3, 2},
{-1, -3, 2},
{-2, -1, 3}, /* 0 degrees yaw, look straight up */
{2, -1, -3} /* look straight down */
};
int vec_to_st[6][3] = {
{-2, 3, 1},
{2, 3, -1},
{1, 3, 2},
{-1, 3, -2},
{-2, -1, 3},
{-2, 1, -3}
};
void
GL4_SetSky(const char *name, float rotate, int autorotate, const vec3_t axis)
{
char skyname[MAX_QPATH];
int i;
Q_strlcpy(skyname, name, sizeof(skyname));
skyrotate = rotate;
skyautorotate = autorotate;
VectorCopy(axis, skyaxis);
for (i = 0; i < 6; i++)
{
gl4image_t *image;
image = (gl4image_t *)GetSkyImage(skyname, suf[i],
r_palettedtexture->value, (findimage_t)GL4_FindImage);
if (!image)
{
R_Printf(PRINT_ALL, "%s: can't load %s:%s sky\n",
__func__, skyname, suf[i]);
image = gl4_notexture;
}
sky_images[i] = image;
}
sky_min = 1.0 / 512;
sky_max = 511.0 / 512;
}
static void
DrawSkyPolygon(int nump, vec3_t vecs)
{
int i, j;
vec3_t v, av;
float s, t, dv;
int axis;
float *vp;
c_sky++;
/* decide which face it maps to */
VectorCopy(vec3_origin, v);
for (i = 0, vp = vecs; i < nump; i++, vp += 3)
{
VectorAdd(vp, v, v);
}
av[0] = fabs(v[0]);
av[1] = fabs(v[1]);
av[2] = fabs(v[2]);
if ((av[0] > av[1]) && (av[0] > av[2]))
{
if (v[0] < 0)
{
axis = 1;
}
else
{
axis = 0;
}
}
else if ((av[1] > av[2]) && (av[1] > av[0]))
{
if (v[1] < 0)
{
axis = 3;
}
else
{
axis = 2;
}
}
else
{
if (v[2] < 0)
{
axis = 5;
}
else
{
axis = 4;
}
}
/* project new texture coords */
for (i = 0; i < nump; i++, vecs += 3)
{
j = vec_to_st[axis][2];
if (j > 0)
{
dv = vecs[j - 1];
}
else
{
dv = -vecs[-j - 1];
}
if (dv < 0.001)
{
continue; /* don't divide by zero */
}
j = vec_to_st[axis][0];
if (j < 0)
{
s = -vecs[-j - 1] / dv;
}
else
{
s = vecs[j - 1] / dv;
}
j = vec_to_st[axis][1];
if (j < 0)
{
t = -vecs[-j - 1] / dv;
}
else
{
t = vecs[j - 1] / dv;
}
if (s < skymins[0][axis])
{
skymins[0][axis] = s;
}
if (t < skymins[1][axis])
{
skymins[1][axis] = t;
}
if (s > skymaxs[0][axis])
{
skymaxs[0][axis] = s;
}
if (t > skymaxs[1][axis])
{
skymaxs[1][axis] = t;
}
}
}
static void
ClipSkyPolygon(int nump, vec3_t vecs, int stage)
{
float *norm;
float *v;
qboolean front, back;
float d, e;
float dists[MAX_CLIP_VERTS];
int sides[MAX_CLIP_VERTS];
vec3_t newv[2][MAX_CLIP_VERTS];
int newc[2];
int i, j;
if (nump > MAX_CLIP_VERTS - 2)
{
ri.Sys_Error(ERR_DROP, "R_ClipSkyPolygon: MAX_CLIP_VERTS");
}
if (stage == 6)
{
/* fully clipped, so draw it */
DrawSkyPolygon(nump, vecs);
return;
}
front = back = false;
norm = skyclip[stage];
for (i = 0, v = vecs; i < nump; i++, v += 3)
{
d = DotProduct(v, norm);
if (d > ON_EPSILON)
{
front = true;
sides[i] = SIDE_FRONT;
}
else if (d < -ON_EPSILON)
{
back = true;
sides[i] = SIDE_BACK;
}
else
{
sides[i] = SIDE_ON;
}
dists[i] = d;
}
if (!front || !back)
{
/* not clipped */
ClipSkyPolygon(nump, vecs, stage + 1);
return;
}
/* clip it */
sides[i] = sides[0];
dists[i] = dists[0];
VectorCopy(vecs, (vecs + (i * 3)));
newc[0] = newc[1] = 0;
for (i = 0, v = vecs; i < nump; i++, v += 3)
{
switch (sides[i])
{
case SIDE_FRONT:
VectorCopy(v, newv[0][newc[0]]);
newc[0]++;
break;
case SIDE_BACK:
VectorCopy(v, newv[1][newc[1]]);
newc[1]++;
break;
case SIDE_ON:
VectorCopy(v, newv[0][newc[0]]);
newc[0]++;
VectorCopy(v, newv[1][newc[1]]);
newc[1]++;
break;
}
if ((sides[i] == SIDE_ON) ||
(sides[i + 1] == SIDE_ON) ||
(sides[i + 1] == sides[i]))
{
continue;
}
d = dists[i] / (dists[i] - dists[i + 1]);
for (j = 0; j < 3; j++)
{
e = v[j] + d * (v[j + 3] - v[j]);
newv[0][newc[0]][j] = e;
newv[1][newc[1]][j] = e;
}
newc[0]++;
newc[1]++;
}
/* continue */
ClipSkyPolygon(newc[0], newv[0][0], stage + 1);
ClipSkyPolygon(newc[1], newv[1][0], stage + 1);
}
void
GL4_AddSkySurface(msurface_t *fa)
{
int i;
vec3_t verts[MAX_CLIP_VERTS];
glpoly_t *p;
/* calculate vertex values for sky box */
for (p = fa->polys; p; p = p->next)
{
for (i = 0; i < p->numverts; i++)
{
VectorSubtract(p->vertices[i].pos, gl4_origin, verts[i]);
}
ClipSkyPolygon(p->numverts, verts[0], 0);
}
}
void
GL4_ClearSkyBox(void)
{
int i;
for (i = 0; i < 6; i++)
{
skymins[0][i] = skymins[1][i] = 9999;
skymaxs[0][i] = skymaxs[1][i] = -9999;
}
}
static void
MakeSkyVec(float s, float t, int axis, gl4_3D_vtx_t* vert)
{
vec3_t v, b;
int j, k;
float dist = (r_farsee->value == 0) ? 2300.0f : 4096.0f;
b[0] = s * dist;
b[1] = t * dist;
b[2] = dist;
for (j = 0; j < 3; j++)
{
k = st_to_vec[axis][j];
if (k < 0)
{
v[j] = -b[-k - 1];
}
else
{
v[j] = b[k - 1];
}
}
/* avoid bilerp seam */
s = (s + 1) * 0.5;
t = (t + 1) * 0.5;
if (s < sky_min)
{
s = sky_min;
}
else if (s > sky_max)
{
s = sky_max;
}
if (t < sky_min)
{
t = sky_min;
}
else if (t > sky_max)
{
t = sky_max;
}
t = 1.0 - t;
VectorCopy(v, vert->pos);
vert->texCoord[0] = s;
vert->texCoord[1] = t;
vert->lmTexCoord[0] = vert->lmTexCoord[1] = 0.0f;
}
void
GL4_DrawSkyBox(void)
{
int i;
if (skyrotate)
{ /* check for no sky at all */
for (i = 0; i < 6; i++)
{
if ((skymins[0][i] < skymaxs[0][i]) &&
(skymins[1][i] < skymaxs[1][i]))
{
break;
}
}
if (i == 6)
{
return; /* nothing visible */
}
}
// glPushMatrix();
hmm_mat4 origModelMat = gl4state.uni3DData.transModelMat4;
// glTranslatef(gl4_origin[0], gl4_origin[1], gl4_origin[2]);
hmm_vec3 transl = HMM_Vec3(gl4_origin[0], gl4_origin[1], gl4_origin[2]);
hmm_mat4 modMVmat = HMM_MultiplyMat4(origModelMat, HMM_Translate(transl));
if(skyrotate != 0.0f)
{
// glRotatef(gl4_newrefdef.time * skyrotate, skyaxis[0], skyaxis[1], skyaxis[2]);
hmm_vec3 rotAxis = HMM_Vec3(skyaxis[0], skyaxis[1], skyaxis[2]);
modMVmat = HMM_MultiplyMat4(modMVmat, HMM_Rotate(
(skyautorotate ? gl4_newrefdef.time : 1.f) * skyrotate, rotAxis));
}
gl4state.uni3DData.transModelMat4 = modMVmat;
GL4_UpdateUBO3D();
GL4_UseProgram(gl4state.si3Dsky.shaderProgram);
GL4_BindVAO(gl4state.vao3D);
GL4_BindVBO(gl4state.vbo3D);
// TODO: this could all be done in one drawcall.. but.. whatever, it's <= 6 drawcalls/frame
gl4_3D_vtx_t skyVertices[4];
for (i = 0; i < 6; i++)
{
if (skyrotate != 0.0f)
{
skymins[0][i] = -1;
skymins[1][i] = -1;
skymaxs[0][i] = 1;
skymaxs[1][i] = 1;
}
if ((skymins[0][i] >= skymaxs[0][i]) ||
(skymins[1][i] >= skymaxs[1][i]))
{
continue;
}
GL4_Bind(sky_images[skytexorder[i]]->texnum);
MakeSkyVec( skymins [ 0 ] [ i ], skymins [ 1 ] [ i ], i, &skyVertices[0] );
MakeSkyVec( skymins [ 0 ] [ i ], skymaxs [ 1 ] [ i ], i, &skyVertices[1] );
MakeSkyVec( skymaxs [ 0 ] [ i ], skymaxs [ 1 ] [ i ], i, &skyVertices[2] );
MakeSkyVec( skymaxs [ 0 ] [ i ], skymins [ 1 ] [ i ], i, &skyVertices[3] );
GL4_BufferAndDraw3D(skyVertices, 4, GL_TRIANGLE_FAN);
}
// glPopMatrix();
gl4state.uni3DData.transModelMat4 = origModelMat;
GL4_UpdateUBO3D();
}

View file

@ -0,0 +1,311 @@
#ifndef __khrplatform_h_
#define __khrplatform_h_
/*
** Copyright (c) 2008-2018 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
/* Khronos platform-specific types and definitions.
*
* The master copy of khrplatform.h is maintained in the Khronos EGL
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
* The last semantic modification to khrplatform.h was at commit ID:
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
*
* Adopters may modify this file to suit their platform. Adopters are
* encouraged to submit platform specific modifications to the Khronos
* group so that they can be included in future versions of this file.
* Please submit changes by filing pull requests or issues on
* the EGL Registry repository linked above.
*
*
* See the Implementer's Guidelines for information about where this file
* should be located on your system and for more details of its use:
* http://www.khronos.org/registry/implementers_guide.pdf
*
* This file should be included as
* #include <KHR/khrplatform.h>
* by Khronos client API header files that use its types and defines.
*
* The types in khrplatform.h should only be used to define API-specific types.
*
* Types defined in khrplatform.h:
* khronos_int8_t signed 8 bit
* khronos_uint8_t unsigned 8 bit
* khronos_int16_t signed 16 bit
* khronos_uint16_t unsigned 16 bit
* khronos_int32_t signed 32 bit
* khronos_uint32_t unsigned 32 bit
* khronos_int64_t signed 64 bit
* khronos_uint64_t unsigned 64 bit
* khronos_intptr_t signed same number of bits as a pointer
* khronos_uintptr_t unsigned same number of bits as a pointer
* khronos_ssize_t signed size
* khronos_usize_t unsigned size
* khronos_float_t signed 32 bit floating point
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
* nanoseconds
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
* khronos_boolean_enum_t enumerated boolean type. This should
* only be used as a base type when a client API's boolean type is
* an enum. Client APIs which use an integer or other type for
* booleans cannot use this as the base type for their boolean.
*
* Tokens defined in khrplatform.h:
*
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
*
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
*
* Calling convention macros defined in this file:
* KHRONOS_APICALL
* KHRONOS_APIENTRY
* KHRONOS_APIATTRIBUTES
*
* These may be used in function prototypes as:
*
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
* int arg1,
* int arg2) KHRONOS_APIATTRIBUTES;
*/
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
# define KHRONOS_STATIC 1
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APICALL
*-------------------------------------------------------------------------
* This precedes the return type of the function in the function prototype.
*/
#if defined(KHRONOS_STATIC)
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
* header compatible with static linking. */
# define KHRONOS_APICALL
#elif defined(_WIN32)
# define KHRONOS_APICALL __declspec(dllimport)
#elif defined (__SYMBIAN32__)
# define KHRONOS_APICALL IMPORT_C
#elif defined(__ANDROID__)
# define KHRONOS_APICALL __attribute__((visibility("default")))
#else
# define KHRONOS_APICALL
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIENTRY
*-------------------------------------------------------------------------
* This follows the return type of the function and precedes the function
* name in the function prototype.
*/
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
/* Win32 but not WinCE */
# define KHRONOS_APIENTRY __stdcall
#else
# define KHRONOS_APIENTRY
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIATTRIBUTES
*-------------------------------------------------------------------------
* This follows the closing parenthesis of the function prototype arguments.
*/
#if defined (__ARMCC_2__)
#define KHRONOS_APIATTRIBUTES __softfp
#else
#define KHRONOS_APIATTRIBUTES
#endif
/*-------------------------------------------------------------------------
* basic type definitions
*-----------------------------------------------------------------------*/
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
/*
* Using <stdint.h>
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
/*
* To support platform where unsigned long cannot be used interchangeably with
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
* unsigned long long or similar (this results in different C++ name mangling).
* To avoid changes for existing platforms, we restrict usage of intptr_t to
* platforms where the size of a pointer is larger than the size of long.
*/
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
#define KHRONOS_USE_INTPTR_T
#endif
#endif
#elif defined(__VMS ) || defined(__sgi)
/*
* Using <inttypes.h>
*/
#include <inttypes.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
/*
* Win32
*/
typedef __int32 khronos_int32_t;
typedef unsigned __int32 khronos_uint32_t;
typedef __int64 khronos_int64_t;
typedef unsigned __int64 khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__sun__) || defined(__digital__)
/*
* Sun or Digital
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#if defined(__arch64__) || defined(_LP64)
typedef long int khronos_int64_t;
typedef unsigned long int khronos_uint64_t;
#else
typedef long long int khronos_int64_t;
typedef unsigned long long int khronos_uint64_t;
#endif /* __arch64__ */
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif 0
/*
* Hypothetical platform with no float or int64 support
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#define KHRONOS_SUPPORT_INT64 0
#define KHRONOS_SUPPORT_FLOAT 0
#else
/*
* Generic fallback
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#endif
/*
* Types that are (so far) the same on all platforms
*/
typedef signed char khronos_int8_t;
typedef unsigned char khronos_uint8_t;
typedef signed short int khronos_int16_t;
typedef unsigned short int khronos_uint16_t;
/*
* Types that differ between LLP64 and LP64 architectures - in LLP64,
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
* to be the only LLP64 architecture in current use.
*/
#ifdef KHRONOS_USE_INTPTR_T
typedef intptr_t khronos_intptr_t;
typedef uintptr_t khronos_uintptr_t;
#elif defined(_WIN64)
typedef signed long long int khronos_intptr_t;
typedef unsigned long long int khronos_uintptr_t;
#else
typedef signed long int khronos_intptr_t;
typedef unsigned long int khronos_uintptr_t;
#endif
#if defined(_WIN64)
typedef signed long long int khronos_ssize_t;
typedef unsigned long long int khronos_usize_t;
#else
typedef signed long int khronos_ssize_t;
typedef unsigned long int khronos_usize_t;
#endif
#if KHRONOS_SUPPORT_FLOAT
/*
* Float type
*/
typedef float khronos_float_t;
#endif
#if KHRONOS_SUPPORT_INT64
/* Time types
*
* These types can be used to represent a time interval in nanoseconds or
* an absolute Unadjusted System Time. Unadjusted System Time is the number
* of nanoseconds since some arbitrary system event (e.g. since the last
* time the system booted). The Unadjusted System Time is an unsigned
* 64 bit value that wraps back to 0 every 584 years. Time intervals
* may be either signed or unsigned.
*/
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
typedef khronos_int64_t khronos_stime_nanoseconds_t;
#endif
/*
* Dummy value used to pad enum types to 32 bits.
*/
#ifndef KHRONOS_MAX_ENUM
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
#endif
/*
* Enumerated boolean type
*
* Values other than zero should be considered to be true. Therefore
* comparisons should not be made against KHRONOS_TRUE.
*/
typedef enum {
KHRONOS_FALSE = 0,
KHRONOS_TRUE = 1,
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
} khronos_boolean_enum_t;
#endif /* __khrplatform_h_ */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,960 @@
/*
* A header-only typesafe dynamic array implementation for plain C,
* kinda like C++ std::vector. This code is compatible with C++, but should
* only be used with POD (plain old data) types, as it uses memcpy() etc
* instead of copy/move construction/assignment.
* It requires a new type (created with the DA_TYPEDEF(ELEMENT_TYPE, ARRAY_TYPE_NAME)
* macro) for each kind of element you want to put in a dynamic array; however
* the "functions" to manipulate the array are actually macros and the same
* for all element types.
* The array elements are accessed via dynArr.p[i] or da_get(dynArr, i)
* - the latter checks whether i is a valid index and asserts if not.
*
* One thing to keep in mind is that, because of using macros, the arguments to
* the "functions" are usually evaluated more than once, so you should avoid
* putting things with side effect (like function-calls with side effects or i++)
* into them. Notable exceptions are the value arguments (v) of da_push()
* and da_insert(), so it's still ok to do da_push(arr, fun_with_sideffects());
* or da_insert(a, 3, x++);
*
* The function-like da_* macros are short aliases of dg_dynarr_* macros.
* If the short names clash with anything in your code or other headers
* you are using, you can, before #including this header, do
* #define DG_DYNARR_NO_SHORTNAMES
* and use the long dg_dynarr_* forms of the macros instead.
*
* Using this library in your project:
* Put this file somewhere in your project.
* In *one* of your .c/.cpp files, do
* #define DG_DYNARR_IMPLEMENTATION
* #include "DG_dynarr.h"
* to create the implementation of this library in that file.
* You can just #include "DG_dynarr.h" (without the #define) in other source
* files to use it there.
*
* See below this comment block for a usage example.
*
* You can #define your own allocators, assertion and the amount of runtime
* checking of indexes, see CONFIGURATION section in the code for more information.
*
*
* This is heavily inspired by Sean Barrett's stretchy_buffer.h
* ( see: https://github.com/nothings/stb/blob/master/stretchy_buffer.h )
* However I wanted to have a struct that holds the array pointer and the length
* and capacity, so that struct always remains at the same address while the
* array memory might be reallocated.
* I can live with arr.p[i] instead of arr[i], but I like how he managed to use
* macros to create an API that doesn't force the user to specify the stored
* type over and over again, so I stole some of his tricks :-)
*
* This has been tested with GCC 4.8 and clang 3.8 (-std=gnu89, -std=c99 and as C++;
* -std=c89 works if you convert the C++-style comments to C comments) and
* Microsoft Visual Studio 6 and 2010 (32bit) and 2013 (32bit and 64bit).
* I guess it works with all (recentish) C++ compilers and C compilers supporting
* C99 or even C89 + C++ comments (otherwise converting the comments should help).
*
* (C) 2016 Daniel Gibson
*
* LICENSE
* This software is dual-licensed to the public domain and under the following
* license: you are granted a perpetual, irrevocable license to copy, modify,
* publish, and distribute this file as you see fit.
* No warranty implied; use at your own risk.
*/
#if 0 // Usage Example:
#define DG_DYNARR_IMPLEMENTATION // this define is only needed in *one* .c/.cpp file!
#include "DG_dynarr.h"
DA_TYPEDEF(int, MyIntArrType); // creates MyIntArrType - a dynamic array for ints
void printIntArr(MyIntArrType* arr, const char* name)
{
// note that arr is a pointer here, so use *arr in the da_*() functions.
printf("%s = {", name);
if(da_count(*arr) > 0)
printf(" %d", arr->p[0]);
for(int i=1; i<da_count(*arr); ++i)
printf(", %d", arr->p[i]);
printf(" }\n");
}
void myFunction()
{
MyIntArrType a1 = {0}; // make sure to zero out the struct
// instead of = {0}; you could also call da_init(a1);
da_push(a1, 42);
assert(da_count(a1) == 1 && a1.p[0] == 42);
int* addedElements = da_addn_uninit(a1, 3);
assert(da_count(a1) == 4);
for(size_t i=0; i<3; ++i)
addedElements[i] = i+5;
printIntArr(&a1, "a1"); // "a1 = { 42, 5, 6, 7 }"
MyIntArrType a2;
da_init(a2);
da_addn(a2, a1.p, da_count(a1)); // copy all elements from a1 to a2
assert(da_count(a2) == 4);
da_insert(a2, 1, 11);
printIntArr(&a2, "a2"); // "a2 = { 42, 11, 5, 6, 7 }"
da_delete(a2, 2);
printIntArr(&a2, "a2"); // "a2 = { 42, 11, 6, 7 }"
da_deletefast(a2, 0);
printIntArr(&a2, "a2"); // "a2 = { 7, 11, 6 }"
da_push(a1, 3);
printIntArr(&a1, "a1"); // "a1 = { 42, 5, 6, 7, 3 }"
int x=da_pop(a1);
printf("x = %d\n", x); // "x = 3"
printIntArr(&a1, "a1"); // "a1 = { 42, 5, 6, 7 }"
da_free(a1); // make sure not to leak memory!
da_free(a2);
}
#endif // 0 (usage example)
#ifndef DG__DYNARR_H
#define DG__DYNARR_H
// ######### CONFIGURATION #########
// following: some #defines that you can tweak to your liking
// you can reduce some overhead by defining DG_DYNARR_INDEX_CHECK_LEVEL to 2, 1 or 0
#ifndef DG_DYNARR_INDEX_CHECK_LEVEL
// 0: (almost) no index checking
// 1: macros "returning" something return a.p[0] or NULL if the index was invalid
// 2: assertions in all macros taking indexes that make sure they're valid
// 3: 1 and 2
#define DG_DYNARR_INDEX_CHECK_LEVEL 3
#endif // DG_DYNARR_INDEX_CHECK_LEVEL
// you can #define your own DG_DYNARR_ASSERT(condition, msgstring)
// that will be used for all assertions in this code.
#ifndef DG_DYNARR_ASSERT
#include <assert.h>
#define DG_DYNARR_ASSERT(cond, msg) assert((cond) && msg)
#endif
// you can #define DG_DYNARR_OUT_OF_MEMORY to some code that will be executed
// if allocating memory fails
// it's needed only before the #define DG_DYNARR_IMPLEMENTATION #include of
// this header, so the following is here only for reference and commented out
/*
#ifndef DG_DYNARR_OUT_OF_MEMORY
#define DG_DYNARR_OUT_OF_MEMORY DG_DYNARR_ASSERT(0, "Out of Memory!");
#endif
*/
// By default, C's malloc(), realloc() and free() is used to allocate/free heap memory
// (see beginning of "#ifdef DG_DYNARR_IMPLEMENTATION" block below).
// You can #define DG_DYNARR_MALLOC, DG_DYNARR_REALLOC and DG_DYNARR_FREE yourself
// to provide alternative implementations like Win32 Heap(Re)Alloc/HeapFree
// it's needed only before the #define DG_DYNARR_IMPLEMENTATION #include of
// this header, so the following is here only for reference and commented out
/*
#define DG_DYNARR_MALLOC(elemSize, numElems) malloc(elemSize*numElems)
// oldNumElems is not used for C's realloc, but maybe you need it for
// your allocator to copy the old elements over
#define DG_DYNARR_REALLOC(ptr, elemSize, oldNumElems, newCapacity) \
realloc(ptr, elemSize*newCapacity);
#define DG_DYNARR_FREE(ptr) free(ptr)
*/
// if you want to prepend something to the non inline (DG_DYNARR_INLINE) functions,
// like "__declspec(dllexport)" or whatever, #define DG_DYNARR_DEF
#ifndef DG_DYNARR_DEF
// by defaults it's empty.
#define DG_DYNARR_DEF
#endif
// some functions are inline, in case your compiler doesn't like "static inline"
// but wants "__inline__" or something instead, #define DG_DYNARR_INLINE accordingly.
#ifndef DG_DYNARR_INLINE
// for pre-C99 compilers you might have to use something compiler-specific (or maybe only "static")
#ifdef _MSC_VER
#define DG_DYNARR_INLINE static __inline
#else
#define DG_DYNARR_INLINE static inline
#endif
#endif
// ############### Short da_* aliases for the long names ###############
#ifndef DG_DYNARR_NO_SHORTNAMES
// this macro is used to create an array type (struct) for elements of TYPE
// use like DA_TYPEDEF(int, MyIntArrType); MyIntArrType ia = {0}; da_push(ia, 42); ...
#define DA_TYPEDEF(TYPE, NewArrayTypeName) \
DG_DYNARR_TYPEDEF(TYPE, NewArrayTypeName)
// makes sure the array is initialized and can be used.
// either do YourArray arr = {0}; or YourArray arr; da_init(arr);
#define da_init(a) \
dg_dynarr_init(a)
/*
* This allows you to provide an external buffer that'll be used as long as it's big enough
* once you add more elements than buf can hold, fresh memory will be allocated on the heap
* Use like:
* DA_TYPEDEF(double, MyDoubleArrType);
* MyDoubleArrType arr;
* double buf[8];
* dg_dynarr_init_external(arr, buf, 8);
* dg_dynarr_push(arr, 1.23);
* ...
*/
#define da_init_external(a, buf, buf_cap) \
dg_dynarr_init_external(a, buf, buf_cap)
// use this to free the memory allocated by dg_dynarr once you don't need the array anymore
// Note: it is safe to add new elements to the array after da_free()
// it will allocate new memory, just like it would directly after da_init()
#define da_free(a) \
dg_dynarr_free(a)
// add an element to the array (appended at the end)
#define da_push(a, v) \
dg_dynarr_push(a, v)
// add an element to the array (appended at the end)
// does the same as push, just for consistency with addn (like insert and insertn)
#define da_add(a, v) \
dg_dynarr_add(a, v)
// append n elements to a and initialize them from array vals, doesn't return anything
// ! vals (and all other args) are evaluated multiple times !
#define da_addn(a, vals, n) \
dg_dynarr_addn(a, vals, n)
// add n elements to the end of the array and zeroes them with memset()
// returns pointer to first added element, NULL if out of memory (array is empty then)
#define da_addn_zeroed(a, n) \
dg_dynarr_addn_zeroed(a, n)
// add n elements to the end of the array, will remain uninitialized
// returns pointer to first added element, NULL if out of memory (array is empty then)
#define da_addn_uninit(a, n) \
dg_dynarr_addn_uninit(a, n)
// insert a single value v at index idx
#define da_insert(a, idx, v) \
dg_dynarr_insert(a, idx, v)
// insert n elements into a at idx, initialize them from array vals
// doesn't return anything
// ! vals (and all other args) is evaluated multiple times !
#define da_insertn(a, idx, vals, n) \
dg_dynarr_insertn(a, idx, vals, n)
// insert n elements into a at idx and zeroe them with memset()
// returns pointer to first inserted element or NULL if out of memory
#define da_insertn_zeroed(a, idx, n) \
dg_dynarr_insertn_zeroed(a, idx, n)
// insert n uninitialized elements into a at idx;
// returns pointer to first inserted element or NULL if out of memory
#define da_insertn_uninit(a, idx, n) \
dg_dynarr_insertn_uninit(a, idx, n)
// set a single value v at index idx - like "a.p[idx] = v;" but with checks (unless disabled)
#define da_set(a, idx, v) \
dg_dynarr_set(a, idx, v)
// overwrite n elements of a, starting at idx, with values from array vals
// doesn't return anything
// ! vals (and all other args) is evaluated multiple times !
#define da_setn(a, idx, vals, n) \
dg_dynarr_setn(a, idx, vals, n)
// delete the element at idx, moving all following elements (=> keeps order)
#define da_delete(a, idx) \
dg_dynarr_delete(a, idx)
// delete n elements starting at idx, moving all following elements (=> keeps order)
#define da_deleten(a, idx, n) \
dg_dynarr_deleten(a, idx, n)
// delete the element at idx, move the last element there (=> doesn't keep order)
#define da_deletefast(a, idx) \
dg_dynarr_deletefast(a, idx)
// delete n elements starting at idx, move the last n elements there (=> doesn't keep order)
#define da_deletenfast(a, idx, n) \
dg_dynarr_deletenfast(a, idx, n)
// removes all elements from the array, but does not free the buffer
// (if you want to free the buffer too, just use da_free())
#define da_clear(a) \
dg_dynarr_clear(a)
// sets the logical number of elements in the array
// if cnt > dg_dynarr_count(a), the logical count will be increased accordingly
// and the new elements will be uninitialized
#define da_setcount(a, cnt) \
dg_dynarr_setcount(a, cnt)
// make sure the array can store cap elements without reallocating
// logical count remains unchanged
#define da_reserve(a, cap) \
dg_dynarr_reserve(a, cap)
// this makes sure a only uses as much memory as for its elements
// => maybe useful if a used to contain a huge amount of elements,
// but you deleted most of them and want to free some memory
// Note however that this implies an allocation and copying the remaining
// elements, so only do this if it frees enough memory to be worthwhile!
#define da_shrink_to_fit(a) \
dg_dynarr_shrink_to_fit(a)
// removes and returns the last element of the array
#define da_pop(a) \
dg_dynarr_pop(a)
// returns the last element of the array
#define da_last(a) \
dg_dynarr_last(a)
// returns the pointer *to* the last element of the array
// (in contrast to dg_dynarr_end() which returns a pointer *after* the last element)
// returns NULL if array is empty
#define da_lastptr(a) \
dg_dynarr_lastptr(a)
// get element at index idx (like a.p[idx]), but with checks
// (unless you disabled them with #define DG_DYNARR_INDEX_CHECK_LEVEL 0)
#define da_get(a, idx) \
dg_dynarr_get(a,idx)
// get pointer to element at index idx (like &a.p[idx]), but with checks
// and it returns NULL if idx is invalid
#define da_getptr(a, idx) \
dg_dynarr_getptr(a, idx)
// returns a pointer to the first element of the array
// (together with dg_dynarr_end() you can do C++-style iterating)
#define da_begin(a) \
dg_dynarr_begin(a)
// returns a pointer to the past-the-end element of the array
// Allows C++-style iterating, in case you're into that kind of thing:
// for(T *it=da_begin(a), *end=da_end(a); it!=end; ++it) foo(*it);
// (see da_lastptr() to get a pointer *to* the last element)
#define da_end(a) \
dg_dynarr_end(a)
// returns (logical) number of elements currently in the array
#define da_count(a) \
dg_dynarr_count(a)
// get the current reserved capacity of the array
#define da_capacity(a) \
dg_dynarr_capacity(a)
// returns 1 if the array is empty, else 0
#define da_empty(a) \
dg_dynarr_empty(a)
// returns 1 if the last (re)allocation when inserting failed (Out Of Memory)
// or if the array has never allocated any memory yet, else 0
// deleting the contents when growing fails instead of keeping old may seem
// a bit uncool, but it's simple and OOM should rarely happen on modern systems
// anyway - after all you need to deplete both RAM and swap/pagefile.sys
#define da_oom(a) \
dg_dynarr_oom(a)
// sort a using the given qsort()-comparator cmp
// (just a slim wrapper around qsort())
#define da_sort(a, cmp) \
dg_dynarr_sort(a, cmp)
#endif // DG_DYNARR_NO_SHORTNAMES
// ######### Implementation of the actual macros (using the long names) ##########
// use like DG_DYNARR_TYPEDEF(int, MyIntArrType); MyIntArrType ia = {0}; dg_dynarr_push(ia, 42); ...
#define DG_DYNARR_TYPEDEF(TYPE, NewArrayTypeName) \
typedef struct { TYPE* p; dg__dynarr_md md; } NewArrayTypeName;
// makes sure the array is initialized and can be used.
// either do YourArray arr = {0}; or YourArray arr; dg_dynarr_init(arr);
#define dg_dynarr_init(a) \
dg__dynarr_init((void**)&(a).p, &(a).md, NULL, 0)
// this allows you to provide an external buffer that'll be used as long as it's big enough
// once you add more elements than buf can hold, fresh memory will be allocated on the heap
#define dg_dynarr_init_external(a, buf, buf_cap) \
dg__dynarr_init((void**)&(a).p, &(a).md, (buf), (buf_cap))
// use this to free the memory allocated by dg_dynarr
// Note: it is safe to add new elements to the array after dg_dynarr_free()
// it will allocate new memory, just like it would directly after dg_dynarr_init()
#define dg_dynarr_free(a) \
dg__dynarr_free((void**)&(a).p, &(a).md)
// add an element to the array (appended at the end)
#define dg_dynarr_push(a, v) \
(dg__dynarr_maybegrowadd(dg__dynarr_unp(a), 1) ? (((a).p[(a).md.cnt++] = (v)),0) : 0)
// add an element to the array (appended at the end)
// does the same as push, just for consistency with addn (like insert and insertn)
#define dg_dynarr_add(a, v) \
dg_dynarr_push((a), (v))
// append n elements to a and initialize them from array vals, doesn't return anything
// ! vals (and all other args) are evaluated multiple times !
#define dg_dynarr_addn(a, vals, n) do { \
DG_DYNARR_ASSERT((vals)!=NULL, "Don't pass NULL als vals to dg_dynarr_addn!"); \
if((vals)!=NULL && dg__dynarr_add(dg__dynarr_unp(a), n, 0)) { \
size_t i_=(a).md.cnt-(n), v_=0; \
while(i_<(a).md.cnt) (a).p[i_++]=(vals)[v_++]; \
} } DG__DYNARR_WHILE0
// add n elements to the end of the array and zeroe them with memset()
// returns pointer to first added element, NULL if out of memory (array is empty then)
#define dg_dynarr_addn_zeroed(a, n) \
(dg__dynarr_add(dg__dynarr_unp(a), (n), 1) ? &(a).p[(a).md.cnt-(size_t)(n)] : NULL)
// add n elements to the end of the array, which are uninitialized
// returns pointer to first added element, NULL if out of memory (array is empty then)
#define dg_dynarr_addn_uninit(a, n) \
(dg__dynarr_add(dg__dynarr_unp(a), (n), 0) ? &(a).p[(a).md.cnt-(size_t)(n)] : NULL)
// insert a single value v at index idx
#define dg_dynarr_insert(a, idx, v) \
(dg__dynarr_checkidxle((a),(idx)), \
dg__dynarr_insert(dg__dynarr_unp(a), (idx), 1, 0), \
(a).p[dg__dynarr_idx((a).md, (idx))] = (v))
// insert n elements into a at idx, initialize them from array vals
// doesn't return anything
// ! vals (and all other args) is evaluated multiple times !
#define dg_dynarr_insertn(a, idx, vals, n) do { \
DG_DYNARR_ASSERT((vals)!=NULL, "Don't pass NULL as vals to dg_dynarr_insertn!"); \
dg__dynarr_checkidxle((a),(idx)); \
if((vals)!=NULL && dg__dynarr_insert(dg__dynarr_unp(a), (idx), (n), 0)){ \
size_t i_=(idx), v_=0, e_=(idx)+(n); \
while(i_ < e_) (a).p[i_++] = (vals)[v_++]; \
}} DG__DYNARR_WHILE0
// insert n elements into a at idx and zeroe them with memset()
// returns pointer to first inserted element or NULL if out of memory
#define dg_dynarr_insertn_zeroed(a, idx, n) \
(dg__dynarr_checkidxle((a),(idx)), \
dg__dynarr_insert(dg__dynarr_unp(a), (idx), (n), 1) \
? &(a).p[dg__dynarr_idx((a).md, (idx))] : NULL)
// insert n uninitialized elements into a at idx;
// returns pointer to first inserted element or NULL if out of memory
#define dg_dynarr_insertn_uninit(a, idx, n) \
(dg__dynarr_checkidxle((a),(idx)), \
dg__dynarr_insert(dg__dynarr_unp(a), idx, n, 0) \
? &(a).p[dg__dynarr_idx((a).md, (idx))] : NULL)
// set a single value v at index idx - like "a.p[idx] = v;" but with checks (unless disabled)
#define dg_dynarr_set(a, idx, v) \
(dg__dynarr_checkidx((a),(idx)), \
(a).p[dg__dynarr_idx((a).md, (idx))] = (v))
// overwrite n elements of a, starting at idx, with values from array vals
// doesn't return anything
// ! vals (and all other args) is evaluated multiple times !
#define dg_dynarr_setn(a, idx, vals, n) do { \
DG_DYNARR_ASSERT((vals)!=NULL, "Don't pass NULL as vals to dg_dynarr_setn!"); \
size_t idx_=(idx); size_t end_=idx_+(size_t)n; \
dg__dynarr_checkidx((a),idx_); dg__dynarr_checkidx((a),end_-1); \
if((vals)!=NULL && idx_ < (a).md.cnt && end_ <= (a).md.cnt) { \
size_t v_=0; \
while(idx_ < end_) (a).p[idx_++] = (vals)[v_++]; \
}} DG__DYNARR_WHILE0
// delete the element at idx, moving all following elements (=> keeps order)
#define dg_dynarr_delete(a, idx) \
(dg__dynarr_checkidx((a),(idx)), dg__dynarr_delete(dg__dynarr_unp(a), (idx), 1))
// delete n elements starting at idx, moving all following elements (=> keeps order)
#define dg_dynarr_deleten(a, idx, n) \
(dg__dynarr_checkidx((a),(idx)), dg__dynarr_delete(dg__dynarr_unp(a), (idx), (n)))
// TODO: check whether idx+n < count?
// delete the element at idx, move the last element there (=> doesn't keep order)
#define dg_dynarr_deletefast(a, idx) \
(dg__dynarr_checkidx((a),(idx)), dg__dynarr_deletefast(dg__dynarr_unp(a), (idx), 1))
// delete n elements starting at idx, move the last n elements there (=> doesn't keep order)
#define dg_dynarr_deletenfast(a, idx, n) \
(dg__dynarr_checkidx((a),(idx)), dg__dynarr_deletefast(dg__dynarr_unp(a), idx, n))
// TODO: check whether idx+n < count?
// removes all elements from the array, but does not free the buffer
// (if you want to free the buffer too, just use dg_dynarr_free())
#define dg_dynarr_clear(a) \
((a).md.cnt=0)
// sets the logical number of elements in the array
// if cnt > dg_dynarr_count(a), the logical count will be increased accordingly
// and the new elements will be uninitialized
#define dg_dynarr_setcount(a, n) \
(dg__dynarr_maybegrow(dg__dynarr_unp(a), (n)) ? ((a).md.cnt = (n)) : 0)
// make sure the array can store cap elements without reallocating
// logical count remains unchanged
#define dg_dynarr_reserve(a, cap) \
dg__dynarr_maybegrow(dg__dynarr_unp(a), (cap))
// this makes sure a only uses as much memory as for its elements
// => maybe useful if a used to contain a huge amount of elements,
// but you deleted most of them and want to free some memory
// Note however that this implies an allocation and copying the remaining
// elements, so only do this if it frees enough memory to be worthwhile!
#define dg_dynarr_shrink_to_fit(a) \
dg__dynarr_shrink_to_fit(dg__dynarr_unp(a))
#if (DG_DYNARR_INDEX_CHECK_LEVEL == 1) || (DG_DYNARR_INDEX_CHECK_LEVEL == 3)
// removes and returns the last element of the array
#define dg_dynarr_pop(a) \
(dg__dynarr_check_notempty((a), "Don't pop an empty array!"), \
(a).p[((a).md.cnt > 0) ? (--(a).md.cnt) : 0])
// returns the last element of the array
#define dg_dynarr_last(a) \
(dg__dynarr_check_notempty((a), "Don't call da_last() on an empty array!"), \
(a).p[((a).md.cnt > 0) ? ((a).md.cnt-1) : 0])
#elif (DG_DYNARR_INDEX_CHECK_LEVEL == 0) || (DG_DYNARR_INDEX_CHECK_LEVEL == 2)
// removes and returns the last element of the array
#define dg_dynarr_pop(a) \
(dg__dynarr_check_notempty((a), "Don't pop an empty array!"), \
(a).p[--(a).md.cnt])
// returns the last element of the array
#define dg_dynarr_last(a) \
(dg__dynarr_check_notempty((a), "Don't call da_last() on an empty array!"), \
(a).p[(a).md.cnt-1])
#else // invalid DG_DYNARR_INDEX_CHECK_LEVEL
#error Invalid index check level DG_DYNARR_INDEX_CHECK_LEVEL (must be 0-3) !
#endif // DG_DYNARR_INDEX_CHECK_LEVEL
// returns the pointer *to* the last element of the array
// (in contrast to dg_dynarr_end() which returns a pointer *after* the last element)
// returns NULL if array is empty
#define dg_dynarr_lastptr(a) \
(((a).md.cnt > 0) ? ((a).p + (a).md.cnt - 1) : NULL)
// get element at index idx (like a.p[idx]), but with checks
// (unless you disabled them with #define DG_DYNARR_INDEX_CHECK_LEVEL 0)
#define dg_dynarr_get(a, idx) \
(dg__dynarr_checkidx((a),(idx)), (a).p[dg__dynarr_idx((a).md, (idx))])
// get pointer to element at index idx (like &a.p[idx]), but with checks
// (unless you disabled them with #define DG_DYNARR_INDEX_CHECK_LEVEL 0)
// if index-checks are disabled, it returns NULL on invalid index (else it asserts() before returning)
#define dg_dynarr_getptr(a, idx) \
(dg__dynarr_checkidx((a),(idx)), \
((size_t)(idx) < (a).md.cnt) ? ((a).p+(size_t)(idx)) : NULL)
// returns a pointer to the first element of the array
// (together with dg_dynarr_end() you can do C++-style iterating)
#define dg_dynarr_begin(a) \
((a).p)
// returns a pointer to the past-the-end element of the array
// Allows C++-style iterating, in case you're into that kind of thing:
// for(T *it=dg_dynarr_begin(a), *end=dg_dynarr_end(a); it!=end; ++it) foo(*it);
// (see dg_dynarr_lastptr() to get a pointer *to* the last element)
#define dg_dynarr_end(a) \
((a).p + (a).md.cnt)
// returns (logical) number of elements currently in the array
#define dg_dynarr_count(a) \
((a).md.cnt)
// get the current reserved capacity of the array
#define dg_dynarr_capacity(a) \
((a).md.cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB)
// returns 1 if the array is empty, else 0
#define dg_dynarr_empty(a) \
((a).md.cnt == 0)
// returns 1 if the last (re)allocation when inserting failed (Out Of Memory)
// or if the array has never allocated any memory yet, else 0
// deleting the contents when growing fails instead of keeping old may seem
// a bit uncool, but it's simple and OOM should rarely happen on modern systems
// anyway - after all you need to deplete both RAM and swap/pagefile.sys
// or deplete the address space, which /might/ happen with 32bit applications
// but probably not with 64bit (at least in the foreseeable future)
#define dg_dynarr_oom(a) \
((a).md.cap == 0)
// sort a using the given qsort()-comparator cmp
// (just a slim wrapper around qsort())
#define dg_dynarr_sort(a, cmp) \
qsort((a).p, (a).md.cnt, sizeof((a).p[0]), (cmp))
// ######### Implementation-Details that are not part of the API ##########
#include <stdlib.h> // size_t, malloc(), free(), realloc()
#include <string.h> // memset(), memcpy(), memmove()
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
size_t cnt; // logical number of elements
size_t cap; // cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB is actual capacity (in elements, *not* bytes!)
// if(cap & DG__DYNARR_SIZE_T_MSB) the current memory is not allocated by dg_dynarr,
// but was set with dg_dynarr_init_external()
// that's handy to give an array a base-element storage on the stack, for example
// TODO: alternatively, we could introduce a flag field to this struct and use that,
// so we don't have to calculate & everytime cap is needed
} dg__dynarr_md;
// I used to have the following in an enum, but MSVC assumes enums are always 32bit ints
static const size_t DG__DYNARR_SIZE_T_MSB = ((size_t)1) << (sizeof(size_t)*8 - 1);
static const size_t DG__DYNARR_SIZE_T_ALL_BUT_MSB = (((size_t)1) << (sizeof(size_t)*8 - 1))-1;
// "unpack" the elements of an array struct for use with helper functions
// (to void** arr, dg__dynarr_md* md, size_t itemsize)
#define dg__dynarr_unp(a) \
(void**)&(a).p, &(a).md, sizeof((a).p[0])
// MSVC warns about "conditional expression is constant" when using the
// do { ... } while(0) idiom in macros..
#ifdef _MSC_VER
#if _MSC_VER >= 1400 // MSVC 2005 and newer
// people claim MSVC 2005 and newer support __pragma, even though it's only documented
// for 2008+ (https://msdn.microsoft.com/en-us/library/d9x1s805%28v=vs.90%29.aspx)
// the following workaround is based on
// http://cnicholson.net/2009/03/stupid-c-tricks-dowhile0-and-c4127/
#define DG__DYNARR_WHILE0 \
__pragma(warning(push)) \
__pragma(warning(disable:4127)) \
while(0) \
__pragma(warning(pop))
#else // older MSVC versions don't support __pragma - I heard this helps for them
#define DG__DYNARR_WHILE0 while(0,0)
#endif
#else // other compilers
#define DG__DYNARR_WHILE0 while(0)
#endif // _MSC_VER
#if (DG_DYNARR_INDEX_CHECK_LEVEL == 2) || (DG_DYNARR_INDEX_CHECK_LEVEL == 3)
#define dg__dynarr_checkidx(a,i) \
DG_DYNARR_ASSERT((size_t)i < a.md.cnt, "index out of bounds!")
// special case for insert operations: == cnt is also ok, insert will append then
#define dg__dynarr_checkidxle(a,i) \
DG_DYNARR_ASSERT((size_t)i <= a.md.cnt, "index out of bounds!")
#define dg__dynarr_check_notempty(a, msg) \
DG_DYNARR_ASSERT(a.md.cnt > 0, msg)
#elif (DG_DYNARR_INDEX_CHECK_LEVEL == 0) || (DG_DYNARR_INDEX_CHECK_LEVEL == 1)
// no assertions that check if index is valid
#define dg__dynarr_checkidx(a,i) (void)0
#define dg__dynarr_checkidxle(a,i) (void)0
#define dg__dynarr_check_notempty(a, msg) (void)0
#else // invalid DG_DYNARR_INDEX_CHECK_LEVEL
#error Invalid index check level DG_DYNARR_INDEX_CHECK_LEVEL (must be 0-3) !
#endif // DG_DYNARR_INDEX_CHECK_LEVEL
#if (DG_DYNARR_INDEX_CHECK_LEVEL == 1) || (DG_DYNARR_INDEX_CHECK_LEVEL == 3)
// the given index, if valid, else 0
#define dg__dynarr_idx(md,i) \
(((size_t)(i) < md.cnt) ? (size_t)(i) : 0)
#elif (DG_DYNARR_INDEX_CHECK_LEVEL == 0) || (DG_DYNARR_INDEX_CHECK_LEVEL == 2)
// don't check and default to 0 if invalid, but just use the given value
#define dg__dynarr_idx(md,i) (size_t)(i)
#else // invalid DG_DYNARR_INDEX_CHECK_LEVEL
#error Invalid index check level DG_DYNARR_INDEX_CHECK_LEVEL (must be 0-3) !
#endif // DG_DYNARR_INDEX_CHECK_LEVEL
// the functions allocating/freeing memory are not implemented inline, but
// in the #ifdef DG_DYNARR_IMPLEMENTATION section
// one reason is that dg__dynarr_grow has the most code in it, the other is
// that windows has weird per-dll heaps so free() or realloc() should be
// called from code in the same dll that allocated the memory - these kind
// of wrapper functions that end up compiled into the exe or *one* dll
// (instead of inline functions compiled into everything) should ensure that.
DG_DYNARR_DEF void
dg__dynarr_free(void** p, dg__dynarr_md* md);
DG_DYNARR_DEF void
dg__dynarr_shrink_to_fit(void** arr, dg__dynarr_md* md, size_t itemsize);
// grow array to have enough space for at least min_needed elements
// if it fails (OOM), the array will be deleted, a.p will be NULL, a.md.cap and a.md.cnt will be 0
// and the functions returns 0; else (on success) it returns 1
DG_DYNARR_DEF int
dg__dynarr_grow(void** arr, dg__dynarr_md* md, size_t itemsize, size_t min_needed);
// the following functions are implemented inline, because they're quite short
// and mosty implemented in functions so the macros don't get too ugly
DG_DYNARR_INLINE void
dg__dynarr_init(void** p, dg__dynarr_md* md, void* buf, size_t buf_cap)
{
*p = buf;
md->cnt = 0;
if(buf == NULL) md->cap = 0;
else md->cap = (DG__DYNARR_SIZE_T_MSB | buf_cap);
}
DG_DYNARR_INLINE int
dg__dynarr_maybegrow(void** arr, dg__dynarr_md* md, size_t itemsize, size_t min_needed)
{
if((md->cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB) >= min_needed) return 1;
else return dg__dynarr_grow(arr, md, itemsize, min_needed);
}
DG_DYNARR_INLINE int
dg__dynarr_maybegrowadd(void** arr, dg__dynarr_md* md, size_t itemsize, size_t num_add)
{
size_t min_needed = md->cnt+num_add;
if((md->cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB) >= min_needed) return 1;
else return dg__dynarr_grow(arr, md, itemsize, min_needed);
}
DG_DYNARR_INLINE int
dg__dynarr_insert(void** arr, dg__dynarr_md* md, size_t itemsize, size_t idx, size_t n, int init0)
{
// allow idx == md->cnt to append
size_t oldCount = md->cnt;
size_t newCount = oldCount+n;
if(idx <= oldCount && dg__dynarr_maybegrow(arr, md, itemsize, newCount))
{
unsigned char* p = (unsigned char*)*arr; // *arr might have changed in dg__dynarr_grow()!
// move all existing items after a[idx] to a[idx+n]
if(idx < oldCount) memmove(p+(idx+n)*itemsize, p+idx*itemsize, itemsize*(oldCount - idx));
// if the memory is supposed to be zeroed, do that
if(init0) memset(p+idx*itemsize, 0, n*itemsize);
md->cnt = newCount;
return 1;
}
return 0;
}
DG_DYNARR_INLINE int
dg__dynarr_add(void** arr, dg__dynarr_md* md, size_t itemsize, size_t n, int init0)
{
size_t cnt = md->cnt;
if(dg__dynarr_maybegrow(arr, md, itemsize, cnt+n))
{
unsigned char* p = (unsigned char*)*arr; // *arr might have changed in dg__dynarr_grow()!
// if the memory is supposed to be zeroed, do that
if(init0) memset(p+cnt*itemsize, 0, n*itemsize);
md->cnt += n;
return 1;
}
return 0;
}
DG_DYNARR_INLINE void
dg__dynarr_delete(void** arr, dg__dynarr_md* md, size_t itemsize, size_t idx, size_t n)
{
size_t cnt = md->cnt;
if(idx < cnt)
{
if(idx+n >= cnt) md->cnt = idx; // removing last element(s) => just reduce count
else
{
unsigned char* p = (unsigned char*)*arr;
// move all items following a[idx+n] to a[idx]
memmove(p+itemsize*idx, p+itemsize*(idx+n), itemsize*(cnt - (idx+n)));
md->cnt -= n;
}
}
}
DG_DYNARR_INLINE void
dg__dynarr_deletefast(void** arr, dg__dynarr_md* md, size_t itemsize, size_t idx, size_t n)
{
size_t cnt = md->cnt;
if(idx < cnt)
{
if(idx+n >= cnt) md->cnt = idx; // removing last element(s) => just reduce count
else
{
unsigned char* p = (unsigned char*)*arr;
// copy the last n items to a[idx] - but handle the case that
// the array has less than n elements left after the deleted elements
size_t numItemsAfterDeleted = cnt - (idx+n);
size_t m = (n < numItemsAfterDeleted) ? n : numItemsAfterDeleted;
memcpy(p+itemsize*idx, p+itemsize*(cnt - m), itemsize*m);
md->cnt -= n;
}
}
}
#ifdef __cplusplus
} // extern "C"
#endif
#endif // DG__DYNARR_H
// ############## Implementation of non-inline functions ##############
#ifdef DG_DYNARR_IMPLEMENTATION
// by default, C's malloc(), realloc() and free() is used to allocate/free heap memory.
// you can #define DG_DYNARR_MALLOC, DG_DYNARR_REALLOC and DG_DYNARR_FREE
// to provide alternative implementations like Win32 Heap(Re)Alloc/HeapFree
//
#ifndef DG_DYNARR_MALLOC
#define DG_DYNARR_MALLOC(elemSize, numElems) malloc(elemSize*numElems)
// oldNumElems is not used here, but maybe you need it for your allocator
// to copy the old elements over
#define DG_DYNARR_REALLOC(ptr, elemSize, oldNumElems, newCapacity) \
realloc(ptr, elemSize*newCapacity);
#define DG_DYNARR_FREE(ptr) free(ptr)
#endif
// you can #define DG_DYNARR_OUT_OF_MEMORY to some code that will be executed
// if allocating memory fails
#ifndef DG_DYNARR_OUT_OF_MEMORY
#define DG_DYNARR_OUT_OF_MEMORY DG_DYNARR_ASSERT(0, "Out of Memory!");
#endif
#ifdef __cplusplus
extern "C" {
#endif
DG_DYNARR_DEF void
dg__dynarr_free(void** p, dg__dynarr_md* md)
{
// only free memory if it doesn't point to external memory
if(!(md->cap & DG__DYNARR_SIZE_T_MSB))
{
DG_DYNARR_FREE(*p);
*p = NULL;
md->cap = 0;
}
md->cnt = 0;
}
DG_DYNARR_DEF int
dg__dynarr_grow(void** arr, dg__dynarr_md* md, size_t itemsize, size_t min_needed)
{
size_t cap = md->cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB;
DG_DYNARR_ASSERT(min_needed > cap, "dg__dynarr_grow() should only be called if storage actually needs to grow!");
if(min_needed < DG__DYNARR_SIZE_T_MSB)
{
size_t newcap = (cap > 4) ? (2*cap) : 8; // allocate for at least 8 elements
// make sure not to set DG__DYNARR_SIZE_T_MSB (unlikely anyway)
if(newcap >= DG__DYNARR_SIZE_T_MSB) newcap = DG__DYNARR_SIZE_T_MSB-1;
if(min_needed > newcap) newcap = min_needed;
// the memory was allocated externally, don't free it, just copy contents
if(md->cap & DG__DYNARR_SIZE_T_MSB)
{
void* p = DG_DYNARR_MALLOC(itemsize, newcap);
if(p != NULL) memcpy(p, *arr, itemsize*md->cnt);
*arr = p;
}
else
{
void* p = DG_DYNARR_REALLOC(*arr, itemsize, md->cnt, newcap);
if(p == NULL) DG_DYNARR_FREE(*arr); // realloc failed, at least don't leak memory
*arr = p;
}
// TODO: handle OOM by setting highest bit of count and keeping old data?
if(*arr) md->cap = newcap;
else
{
md->cap = 0;
md->cnt = 0;
DG_DYNARR_OUT_OF_MEMORY ;
return 0;
}
return 1;
}
DG_DYNARR_ASSERT(min_needed < DG__DYNARR_SIZE_T_MSB, "Arrays must stay below SIZE_T_MAX/2 elements!");
return 0;
}
DG_DYNARR_DEF void
dg__dynarr_shrink_to_fit(void** arr, dg__dynarr_md* md, size_t itemsize)
{
// only do this if we allocated the memory ourselves
if(!(md->cap & DG__DYNARR_SIZE_T_MSB))
{
size_t cnt = md->cnt;
if(cnt == 0) dg__dynarr_free(arr, md);
else if((md->cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB) > cnt)
{
void* p = DG_DYNARR_MALLOC(itemsize, cnt);
if(p != NULL)
{
memcpy(p, *arr, cnt*itemsize);
md->cap = cnt;
DG_DYNARR_FREE(*arr);
*arr = p;
}
}
}
}
#ifdef __cplusplus
} // extern "C"
#endif
#endif // DG_DYNARR_IMPLEMENTATION

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,562 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (C) 2016-2017 Daniel Gibson
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Local header for the OpenGL4 refresher.
*
* =======================================================================
*/
#ifndef SRC_CLIENT_REFRESH_GL4_HEADER_LOCAL_H_
#define SRC_CLIENT_REFRESH_GL4_HEADER_LOCAL_H_
#ifdef IN_IDE_PARSER
// this is just a hack to get proper auto-completion in IDEs:
// using system headers for their parsers/indexers but glad for real build
// (in glad glFoo is just a #define to glad_glFoo or sth, which screws up autocompletion)
// (you may have to configure your IDE to #define IN_IDE_PARSER, but not for building!)
#ifdef YQ2_GL3_GLES3
#include <GLES3/gl32.h>
#else // desktop GL4
#define GL_GLEXT_PROTOTYPES 1
#include <GL/gl.h>
#include <GL/glext.h>
#endif
#else
#ifdef YQ2_GL3_GLES3
#include "../glad-gles3/include/glad/glad.h"
// yes, this is a bit hacky, but it works :-P
#define glDepthRange glDepthRangef
#else // desktop GL4
#include "../glad/include/glad/glad.h"
#endif
#endif
#include "../../ref_shared.h"
#include "HandmadeMath.h"
#if 0 // only use this for development ..
#define STUB_ONCE(msg) do { \
static int show=1; \
if(show) { \
show = 0; \
R_Printf(PRINT_ALL, "STUB: %s() %s\n", __FUNCTION__, msg); \
} \
} while(0);
#else // .. so make this a no-op in released code
#define STUB_ONCE(msg)
#endif
// a wrapper around glVertexAttribPointer() to stay sane
// (caller doesn't have to cast to GLintptr and then void*)
static inline void
qglVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLintptr offset)
{
glVertexAttribPointer(index, size, type, normalized, stride, (const void*)offset);
}
static inline void
qglVertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset)
{
glVertexAttribIPointer(index, size, type, stride, (void*)offset);
}
// attribute locations for vertex shaders
enum {
GL4_ATTRIB_POSITION = 0,
GL4_ATTRIB_TEXCOORD = 1, // for normal texture
GL4_ATTRIB_LMTEXCOORD = 2, // for lightmap
GL4_ATTRIB_COLOR = 3, // per-vertex color
GL4_ATTRIB_NORMAL = 4, // vertex normal
GL4_ATTRIB_LIGHTFLAGS = 5 // uint, each set bit means "dyn light i affects this surface"
};
// always using RGBA now, GLES3 on RPi4 doesn't work otherwise
// and I think all modern GPUs prefer 4byte pixels over 3bytes
static const int gl4_solid_format = GL_RGBA;
static const int gl4_alpha_format = GL_RGBA;
static const int gl4_tex_solid_format = GL_RGBA;
static const int gl4_tex_alpha_format = GL_RGBA;
extern unsigned gl4_rawpalette[256];
extern unsigned d_8to24table[256];
typedef struct
{
const char *renderer_string;
const char *vendor_string;
const char *version_string;
const char *glsl_version_string;
int major_version;
int minor_version;
// ----
qboolean anisotropic; // is GL_EXT_texture_filter_anisotropic supported?
qboolean debug_output; // is GL_ARB_debug_output supported?
qboolean stencil; // Do we have a stencil buffer?
qboolean useBigVBO; // workaround for AMDs windows driver for fewer calls to glBufferData()
// ----
float max_anisotropy;
} gl4config_t;
typedef struct
{
GLuint shaderProgram;
GLint uniVblend;
GLint uniLmScalesOrTime; // for 3D it's lmScales, for 2D underwater PP it's time
hmm_vec4 lmScales[4];
} gl4ShaderInfo_t;
typedef struct
{
GLfloat gamma;
GLfloat intensity;
GLfloat intensity2D; // for HUD, menus etc
// entries of std430 UBOs are aligned to multiples of their own size
// so we'll need to pad accordingly for following vec4
GLfloat _padding;
hmm_vec4 color;
} gl4UniCommon_t;
typedef struct
{
hmm_mat4 transMat4;
} gl4Uni2D_t;
typedef struct
{
hmm_mat4 transProjViewMat4; // gl4state.projMat3D * gl4state.viewMat3D - so we don't have to do this in the shader
hmm_mat4 transModelMat4;
GLfloat scroll; // for SURF_FLOWING
GLfloat time; // for warping surfaces like water & possibly other things
GLfloat alpha; // for translucent surfaces (water, glass, ..)
GLfloat overbrightbits; // gl4_overbrightbits, applied to lightmaps (and elsewhere to models)
GLfloat particleFadeFactor; // gl4_particle_fade_factor, higher => less fading out towards edges
GLfloat lightScaleForTurb; // surfaces with SURF_DRAWTURB (water, lava) don't have lightmaps, use this instead
GLfloat _padding[2]; // again, some padding to ensure this has right size
} gl4Uni3D_t;
extern const hmm_mat4 gl4_identityMat4;
typedef struct
{
vec3_t origin;
GLfloat _padding;
vec3_t color;
GLfloat intensity;
} gl4UniDynLight;
typedef struct
{
gl4UniDynLight dynLights[MAX_DLIGHTS];
GLuint numDynLights;
GLfloat _padding[3];
} gl4UniLights_t;
enum {
// width and height used to be 128, so now we should be able to get the same lightmap data
// that used 32 lightmaps before into one, so 4 lightmaps should be enough
BLOCK_WIDTH = 1024,
BLOCK_HEIGHT = 512,
LIGHTMAP_BYTES = 4,
MAX_LIGHTMAPS = 4,
MAX_LIGHTMAPS_PER_SURFACE = MAXLIGHTMAPS // 4
};
typedef struct
{
// TODO: what of this do we need?
qboolean fullscreen;
int prev_mode;
// each lightmap consists of 4 sub-lightmaps allowing changing shadows on the same surface
// used for switching on/off light and stuff like that.
// most surfaces only have one really and the remaining for are filled with dummy data
GLuint lightmap_textureIDs[MAX_LIGHTMAPS][MAX_LIGHTMAPS_PER_SURFACE]; // instead of lightmap_textures+i use lightmap_textureIDs[i]
GLuint currenttexture; // bound to GL_TEXTURE0
int currentlightmap; // lightmap_textureIDs[currentlightmap] bound to GL_TEXTURE1
GLuint currenttmu; // GL_TEXTURE0 or GL_TEXTURE1
// FBO for postprocess effects (like under-water-warping)
GLuint ppFBO;
GLuint ppFBtex; // ppFBO's texture for color buffer
int ppFBtexWidth, ppFBtexHeight;
GLuint ppFBrbo; // ppFBO's renderbuffer object for depth and stencil buffer
qboolean ppFBObound; // is it currently bound (rendered to)?
//float camera_separation;
//enum stereo_modes stereo_mode;
GLuint currentVAO;
GLuint currentVBO;
GLuint currentEBO;
GLuint currentShaderProgram;
GLuint currentUBO;
// NOTE: make sure si2D is always the first shaderInfo (or adapt GL4_ShutdownShaders())
gl4ShaderInfo_t si2D; // shader for rendering 2D with textures
gl4ShaderInfo_t si2Dcolor; // shader for rendering 2D with flat colors
gl4ShaderInfo_t si2DpostProcess; // shader to render postprocess FBO, when *not* underwater
gl4ShaderInfo_t si2DpostProcessWater; // shader to apply water-warp postprocess effect
gl4ShaderInfo_t si3Dlm; // a regular opaque face (e.g. from brush) with lightmap
// TODO: lm-only variants for gl_lightmap 1
gl4ShaderInfo_t si3Dtrans; // transparent is always w/o lightmap
gl4ShaderInfo_t si3DcolorOnly; // used for beams - no lightmaps
gl4ShaderInfo_t si3Dturb; // for water etc - always without lightmap
gl4ShaderInfo_t si3DlmFlow; // for flowing/scrolling things with lightmap (conveyor, ..?)
gl4ShaderInfo_t si3DtransFlow; // for transparent flowing/scrolling things (=> no lightmap)
gl4ShaderInfo_t si3Dsky; // guess what..
gl4ShaderInfo_t si3Dsprite; // for sprites
gl4ShaderInfo_t si3DspriteAlpha; // for sprites with alpha-testing
gl4ShaderInfo_t si3Dalias; // for models
gl4ShaderInfo_t si3DaliasColor; // for models w/ flat colors
// NOTE: make sure siParticle is always the last shaderInfo (or adapt GL4_ShutdownShaders())
gl4ShaderInfo_t siParticle; // for particles. surprising, right?
GLuint vao3D, vbo3D; // for brushes etc, using 10 floats and one uint as vertex input (x,y,z, s,t, lms,lmt, normX,normY,normZ ; lightFlags)
// the next two are for gl4config.useBigVBO == true
int vbo3Dsize;
int vbo3DcurOffset;
GLuint vaoAlias, vboAlias, eboAlias; // for models, using 9 floats as (x,y,z, s,t, r,g,b,a)
GLuint vaoParticle, vboParticle; // for particles, using 9 floats (x,y,z, size,distance, r,g,b,a)
// UBOs and their data
gl4UniCommon_t uniCommonData;
gl4Uni2D_t uni2DData;
gl4Uni3D_t uni3DData;
gl4UniLights_t uniLightsData;
GLuint uniCommonUBO;
GLuint uni2DUBO;
GLuint uni3DUBO;
GLuint uniLightsUBO;
hmm_mat4 projMat3D;
hmm_mat4 viewMat3D;
} gl4state_t;
extern gl4config_t gl4config;
extern gl4state_t gl4state;
extern viddef_t vid;
extern refdef_t gl4_newrefdef;
extern int gl4_visframecount; /* bumped when going to a new PVS */
extern int gl4_framecount; /* used for dlight push checking */
extern int gl4_viewcluster, gl4_viewcluster2, gl4_oldviewcluster, gl4_oldviewcluster2;
extern int c_brush_polys, c_alias_polys;
extern qboolean IsHighDPIaware;
/* NOTE: struct image_s* is what re.RegisterSkin() etc return so no gl4image_s!
* (I think the client only passes the pointer around and doesn't know the
* definition of this struct, so this being different from struct image_s
* in ref_gl should be ok)
*/
typedef struct image_s
{
char name[MAX_QPATH]; /* game path, including extension */
imagetype_t type;
int width, height; /* source image */
//int upload_width, upload_height; /* after power of two and picmip */
int registration_sequence; /* 0 = free */
struct msurface_s *texturechain; /* for sort-by-texture world drawing */
GLuint texnum; /* gl texture binding */
float sl, tl, sh, th; /* 0,0 - 1,1 unless part of the scrap */
// qboolean scrap; // currently unused
qboolean has_alpha;
qboolean is_lava; // DG: added for lava brightness hack
} gl4image_t;
enum {MAX_GL4TEXTURES = 1024};
// include this down here so it can use gl4image_t
#include "model.h"
typedef struct
{
int internal_format;
int current_lightmap_texture; // index into gl4state.lightmap_textureIDs[]
//msurface_t *lightmap_surfaces[MAX_LIGHTMAPS]; - no more lightmap chains, lightmaps are rendered multitextured
int allocated[BLOCK_WIDTH];
/* the lightmap texture data needs to be kept in
main memory so texsubimage can update properly */
byte lightmap_buffers[MAX_LIGHTMAPS_PER_SURFACE][4 * BLOCK_WIDTH * BLOCK_HEIGHT];
} gl4lightmapstate_t;
extern gl4model_t *gl4_worldmodel;
extern float gl4depthmin, gl4depthmax;
extern cplane_t frustum[4];
extern vec3_t gl4_origin;
extern gl4image_t *gl4_notexture; /* use for bad textures */
extern gl4image_t *gl4_particletexture; /* little dot for particles */
extern int gl_filter_min;
extern int gl_filter_max;
static inline void
GL4_UseProgram(GLuint shaderProgram)
{
if(shaderProgram != gl4state.currentShaderProgram)
{
gl4state.currentShaderProgram = shaderProgram;
glUseProgram(shaderProgram);
}
}
static inline void
GL4_BindVAO(GLuint vao)
{
if(vao != gl4state.currentVAO)
{
gl4state.currentVAO = vao;
glBindVertexArray(vao);
}
}
static inline void
GL4_BindVBO(GLuint vbo)
{
if(vbo != gl4state.currentVBO)
{
gl4state.currentVBO = vbo;
glBindBuffer(GL_ARRAY_BUFFER, vbo);
}
}
static inline void
GL4_BindEBO(GLuint ebo)
{
if(ebo != gl4state.currentEBO)
{
gl4state.currentEBO = ebo;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
}
}
extern void GL4_BufferAndDraw3D(const gl4_3D_vtx_t* verts, int numVerts, GLenum drawMode);
extern void GL4_RotateForEntity(entity_t *e);
// gl4_sdl.c
extern int GL4_InitContext(void* win);
extern void GL4_GetDrawableSize(int* width, int* height);
extern int GL4_PrepareForWindow(void);
extern qboolean GL4_IsVsyncActive(void);
extern void GL4_EndFrame(void);
extern void GL4_SetVsync(void);
extern void GL4_ShutdownContext(void);
// gl4_misc.c
extern void GL4_InitParticleTexture(void);
extern void GL4_ScreenShot(void);
extern void GL4_SetDefaultState(void);
// gl4_model.c
extern int registration_sequence;
extern void GL4_Mod_Init(void);
extern void GL4_Mod_FreeAll(void);
extern void GL4_BeginRegistration(char *model);
extern struct model_s * GL4_RegisterModel(char *name);
extern void GL4_EndRegistration(void);
extern void GL4_Mod_Modellist_f(void);
extern const byte* GL4_Mod_ClusterPVS(int cluster, const gl4model_t *model);
// gl4_draw.c
extern void GL4_Draw_InitLocal(void);
extern void GL4_Draw_ShutdownLocal(void);
extern gl4image_t * GL4_Draw_FindPic(char *name);
extern void GL4_Draw_GetPicSize(int *w, int *h, char *pic);
extern void GL4_Draw_PicScaled(int x, int y, char *pic, float factor);
extern void GL4_Draw_StretchPic(int x, int y, int w, int h, char *pic);
extern void GL4_Draw_CharScaled(int x, int y, int num, float scale);
extern void GL4_Draw_TileClear(int x, int y, int w, int h, char *pic);
extern void GL4_DrawFrameBufferObject(int x, int y, int w, int h, GLuint fboTexture, const float v_blend[4]);
extern void GL4_Draw_Fill(int x, int y, int w, int h, int c);
extern void GL4_Draw_FadeScreen(void);
extern void GL4_Draw_Flash(const float color[4], float x, float y, float w, float h);
extern void GL4_Draw_StretchRaw(int x, int y, int w, int h, int cols, int rows, const byte *data, int bits);
// gl4_image.c
static inline void
GL4_SelectTMU(GLenum tmu)
{
if(gl4state.currenttmu != tmu)
{
glActiveTexture(tmu);
gl4state.currenttmu = tmu;
}
}
extern void GL4_TextureMode(char *string);
extern void GL4_Bind(GLuint texnum);
extern void GL4_BindLightmap(int lightmapnum);
extern gl4image_t *GL4_LoadPic(char *name, byte *pic, int width, int realwidth,
int height, int realheight, size_t data_size,
imagetype_t type, int bits);
extern gl4image_t *GL4_FindImage(const char *name, imagetype_t type);
extern gl4image_t *GL4_RegisterSkin(char *name);
extern void GL4_ShutdownImages(void);
extern void GL4_FreeUnusedImages(void);
extern qboolean GL4_ImageHasFreeSpace(void);
extern void GL4_ImageList_f(void);
// gl4_light.c
extern int r_dlightframecount;
extern void GL4_MarkSurfaceLights(dlight_t *light, int bit, mnode_t *node,
int r_dlightframecount);
extern void GL4_PushDlights(void);
extern void GL4_LightPoint(entity_t *currententity, vec3_t p, vec3_t color);
extern void GL4_BuildLightMap(msurface_t *surf, int offsetInLMbuf, int stride);
// gl4_lightmap.c
#define GL_LIGHTMAP_FORMAT GL_RGBA
extern void GL4_LM_InitBlock(void);
extern void GL4_LM_UploadBlock(void);
extern qboolean GL4_LM_AllocBlock(int w, int h, int *x, int *y);
extern void GL4_LM_BuildPolygonFromSurface(gl4model_t *currentmodel, msurface_t *fa);
extern void GL4_LM_CreateSurfaceLightmap(msurface_t *surf);
extern void GL4_LM_BeginBuildingLightmaps(gl4model_t *m);
extern void GL4_LM_EndBuildingLightmaps(void);
// gl4_warp.c
extern void GL4_EmitWaterPolys(msurface_t *fa);
extern void GL4_SubdivideSurface(msurface_t *fa, gl4model_t* loadmodel);
extern void GL4_SetSky(const char *name, float rotate, int autorotate, const vec3_t axis);
extern void GL4_DrawSkyBox(void);
extern void GL4_ClearSkyBox(void);
extern void GL4_AddSkySurface(msurface_t *fa);
// gl4_surf.c
extern void GL4_SurfInit(void);
extern void GL4_SurfShutdown(void);
extern void GL4_DrawGLPoly(msurface_t *fa);
extern void GL4_DrawGLFlowingPoly(msurface_t *fa);
extern void GL4_DrawTriangleOutlines(void);
extern void GL4_DrawAlphaSurfaces(void);
extern void GL4_DrawBrushModel(entity_t *e, gl4model_t *currentmodel);
extern void GL4_DrawWorld(void);
extern void GL4_MarkLeaves(void);
// gl4_mesh.c
extern void GL4_DrawAliasModel(entity_t *e);
extern void GL4_ResetShadowAliasModels(void);
extern void GL4_DrawAliasShadows(void);
extern void GL4_ShutdownMeshes(void);
// gl4_shaders.c
extern qboolean GL4_RecreateShaders(void);
extern qboolean GL4_InitShaders(void);
extern void GL4_ShutdownShaders(void);
extern void GL4_UpdateUBOCommon(void);
extern void GL4_UpdateUBO2D(void);
extern void GL4_UpdateUBO3D(void);
extern void GL4_UpdateUBOLights(void);
// ############ Cvars ###########
extern cvar_t *gl_msaa_samples;
extern cvar_t *r_vsync;
extern cvar_t *r_retexturing;
extern cvar_t *r_scale8bittextures;
extern cvar_t *vid_fullscreen;
extern cvar_t *r_mode;
extern cvar_t *r_customwidth;
extern cvar_t *r_customheight;
extern cvar_t *r_2D_unfiltered;
extern cvar_t *r_videos_unfiltered;
extern cvar_t *gl_nolerp_list;
extern cvar_t *r_lerp_list;
extern cvar_t *gl_nobind;
extern cvar_t *r_lockpvs;
extern cvar_t *r_novis;
extern cvar_t *r_cull;
extern cvar_t *gl_zfix;
extern cvar_t *r_fullbright;
extern cvar_t *r_norefresh;
extern cvar_t *gl_lefthand;
extern cvar_t *r_gunfov;
extern cvar_t *r_farsee;
extern cvar_t *r_drawworld;
extern cvar_t *vid_gamma;
extern cvar_t *gl4_intensity;
extern cvar_t *gl4_intensity_2D;
extern cvar_t *gl_anisotropic;
extern cvar_t *gl_texturemode;
extern cvar_t *r_lightlevel;
extern cvar_t *gl4_overbrightbits;
extern cvar_t *gl4_particle_fade_factor;
extern cvar_t *gl4_particle_square;
extern cvar_t *gl4_colorlight;
extern cvar_t *gl_polyblend;
extern cvar_t *r_modulate;
extern cvar_t *gl_lightmap;
extern cvar_t *gl_shadows;
extern cvar_t *r_fixsurfsky;
extern cvar_t *r_palettedtexture;
extern cvar_t *r_validation;
extern cvar_t *gl4_debugcontext;
#endif /* SRC_CLIENT_REFRESH_GL4_HEADER_LOCAL_H_ */

View file

@ -0,0 +1,163 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Header for the model stuff.
*
* =======================================================================
*/
#ifndef SRC_CLIENT_REFRESH_GL4_HEADER_MODEL_H_
#define SRC_CLIENT_REFRESH_GL4_HEADER_MODEL_H_
// used for vertex array elements when drawing brushes, sprites, sky and more
// (ok, it has the layout used for rendering brushes, but is not used there)
typedef struct gl4_3D_vtx_s {
vec3_t pos;
float texCoord[2];
float lmTexCoord[2]; // lightmap texture coordinate (sometimes unused)
vec3_t normal;
GLuint lightFlags; // bit i set means: dynlight i affects surface
} gl4_3D_vtx_t;
// used for vertex array elements when drawing models
typedef struct gl4_alias_vtx_s {
GLfloat pos[3];
GLfloat texCoord[2];
GLfloat color[4];
} gl4_alias_vtx_t;
/* in memory representation */
typedef struct glpoly_s
{
struct glpoly_s *next;
struct glpoly_s *chain;
int numverts;
int flags; /* for SURF_UNDERWATER (not needed anymore?) */
gl4_3D_vtx_t vertices[4]; /* variable sized */
} glpoly_t;
typedef struct msurface_s
{
int visframe; /* should be drawn when node is crossed */
cplane_t *plane;
int flags;
int firstedge; /* look up in model->surfedges[], negative numbers */
int numedges; /* are backwards edges */
short texturemins[2];
short extents[2];
int light_s, light_t; /* gl lightmap coordinates */
int dlight_s, dlight_t; /* gl lightmap coordinates for dynamic lightmaps */
glpoly_t *polys; /* multiple if warped */
struct msurface_s *texturechain;
// struct msurface_s *lightmapchain; not used/needed anymore
mtexinfo_t *texinfo;
/* lighting info */
int dlightframe;
int dlightbits;
int lightmaptexturenum;
byte styles[MAXLIGHTMAPS]; // MAXLIGHTMAPS = MAX_LIGHTMAPS_PER_SURFACE (defined in local.h)
// I think cached_light is not used/needed anymore
//float cached_light[MAXLIGHTMAPS]; /* values currently used in lightmap */
byte *samples; /* [numstyles*surfsize] */
} msurface_t;
/* Whole model */
// this, must be struct model_s, not gl4model_s,
// because struct model_s* is returned by re.RegisterModel()
typedef struct model_s
{
char name[MAX_QPATH];
int registration_sequence;
modtype_t type;
int numframes;
int flags;
/* volume occupied by the model graphics */
vec3_t mins, maxs;
float radius;
/* solid volume for clipping */
qboolean clipbox;
vec3_t clipmins, clipmaxs;
/* brush model */
int firstmodelsurface, nummodelsurfaces;
int lightmap; /* only for submodels */
int numsubmodels;
struct model_s *submodels;
int numplanes;
cplane_t *planes;
int numleafs; /* number of visible leafs, not counting 0 */
mleaf_t *leafs;
int numvertexes;
mvertex_t *vertexes;
int numedges;
medge_t *edges;
int numnodes;
int firstnode;
mnode_t *nodes;
int numtexinfo;
mtexinfo_t *texinfo;
int numsurfaces;
msurface_t *surfaces;
int numsurfedges;
int *surfedges;
int nummarksurfaces;
msurface_t **marksurfaces;
dvis_t *vis;
byte *lightdata;
/* for alias models and skins */
gl4image_t *skins[MAX_MD2SKINS];
int extradatasize;
void *extradata;
// submodules
vec3_t origin; // for sounds or lights
} gl4model_t;
#endif /* SRC_CLIENT_REFRESH_GL4_HEADER_MODEL_H_ */