From a06aacd405e06f36a0eb7a878a3104b679c9bd6f Mon Sep 17 00:00:00 2001 From: mid <> Date: Sat, 18 Jan 2025 21:47:19 +0200 Subject: [PATCH] Initial commit --- src/k3.c | 2805 +++++++++++++++++++++++++++++++++++++++++++++++++ src/k3.h | 212 ++++ src/k3batch.c | 224 ++++ src/k3batch.h | 14 + src/k3bloom.c | 286 +++++ src/k3bloom.h | 8 + src/k3font.c | 150 +++ src/k3font.h | 77 ++ src/k3menu.c | 352 +++++++ src/k3menu.h | 127 +++ src/k3water.c | 150 +++ src/k3water.h | 42 + 12 files changed, 4447 insertions(+) create mode 100644 src/k3.c create mode 100644 src/k3.h create mode 100644 src/k3batch.c create mode 100644 src/k3batch.h create mode 100644 src/k3bloom.c create mode 100644 src/k3bloom.h create mode 100644 src/k3font.c create mode 100644 src/k3font.h create mode 100644 src/k3menu.c create mode 100644 src/k3menu.h create mode 100644 src/k3water.c create mode 100644 src/k3water.h diff --git a/src/k3.c b/src/k3.c new file mode 100644 index 0000000..f6930cd --- /dev/null +++ b/src/k3.c @@ -0,0 +1,2805 @@ +#include"k3.h" + +#include"gl.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include"ssort.h" + +#define GL_FROM_K3TEX(k3t) ((k3t) ? (k3t)->tex : 0) +#define GL_FROM_K3MARCHER(k3m) ((GLuint) (uintptr_t) (k3m)) +#define GL_FROM_K3ARBVP(k3m) ((GLuint) (uintptr_t) (k3m)) +#define GL_FROM_K3ARBFP(k3m) ((GLuint) (uintptr_t) (k3m)) +#define GL_FROM_K3GLSL(k3m) ((GLuint) (uintptr_t) (k3m)) + +#define RAST_DIFF 0 +#define RAST_NORM 1 +#define RAST_DISP 2 +#define RAST_LEVELS 3 + +static int k3CPUSkinning = 0; + +int k3IsCore = 0; + +static struct k3GLSLP *basicBlitProgram; +static struct k3GLSLP *basicCubemapProgram; + +struct k3Tex { + GLuint tex; + int cubemap; + uint32_t szX; + uint32_t szY; + uint32_t szZ; +}; + +struct k3Storage { + int16_t ref; + void(*free)(struct k3Storage*); +}; + +struct k3StorageBasic { + struct k3Storage; + GLuint gl; +}; + +void k3StorageRef(struct k3Storage *s) { + s->ref++; +} + +void k3StorageUnref(struct k3Storage *s) { + if(--s->ref == 0) { + s->free(s); + free(s); + } +} + +static void k3StorageBasicFree(struct k3Storage *s) { + glDeleteBuffersARB(1, &((struct k3StorageBasic*) s)->gl); +} +struct k3StorageBasic *k3StorageBasic() { + struct k3StorageBasic *ret = calloc(1, sizeof(*ret)); + ret->ref = 1; + ret->free = k3StorageBasicFree; + glGenBuffersARB(1, &ret->gl); + return ret; +} + +struct k3Offscreen { + GLuint fbo; + struct k3Tex *diffuse; + struct k3Tex *depth; +}; + +struct k3Mdl { + struct { + vec3 *pos; + uint8_t *boneids; + uint16_t *boneweights; + } cpuSkinning; + + size_t verts; + struct k3StorageBasic *vstore; + struct k3StorageBasic *estore; + + uint16_t meshCount; + struct k3Mesh *meshes; + + size_t boneCount; + mat4 *invBind; + uint8_t *boneParents; + + uint16_t animCount; + struct k3Animation **anims; + + vec3 aabb[2]; + + int offV, offN, offC, offU, offB, offT; + + const char *debugname; +}; + +static uint16_t MainWidth, MainHeight; +void k3Resize(uint16_t width, uint16_t height) { + MainWidth = width; + MainHeight = height; +} + +static float Time; +void k3SetTime(float t) { + Time = t; +} + +static void update_aabb(struct k3Mdl *mdl, vec3 *pos) { + glm_vec3_copy(pos[0], mdl->aabb[0]); + glm_vec3_copy(pos[0], mdl->aabb[1]); + for(size_t i = 0; i < mdl->verts; i++) { + glm_vec3_minv(pos[i], mdl->aabb[0], mdl->aabb[0]); + glm_vec3_maxv(pos[i], mdl->aabb[1], mdl->aabb[1]); + } +} + +static int8_t *generate_tangents(size_t verts, size_t indices, uint16_t *inds, vec3 *pos, int8_t *nrm, vec2 *uvs) { + vec4 *tans1 = _mm_malloc(verts * sizeof(*tans1), 16); + vec4 *tans2 = _mm_malloc(verts * sizeof(*tans2), 16); + + memset(tans1, 0, sizeof(*tans1) * verts); + memset(tans2, 0, sizeof(*tans2) * verts); + + bool warningBadTangents = 0; + + for(size_t i = 0; i < indices; i += 3) { + vec3 p1, p2, p3; + glm_vec3_copy(pos[inds[i + 0]], p1); + glm_vec3_copy(pos[inds[i + 1]], p2); + glm_vec3_copy(pos[inds[i + 2]], p3); + + vec2 w1, w2, w3; + glm_vec2_copy(uvs[inds[i + 0]], w1); + glm_vec2_copy(uvs[inds[i + 1]], w2); + glm_vec2_copy(uvs[inds[i + 2]], w3); + + float x1 = p2[0] - p1[0]; + float x2 = p3[0] - p1[0]; + + float y1 = p2[1] - p1[1]; + float y2 = p3[1] - p1[1]; + + float z1 = p2[2] - p1[2]; + float z2 = p3[2] - p1[2]; + + float s1 = w2[0] - w1[0]; + float s2 = w3[0] - w1[0]; + + float t1 = w2[1] - w1[1]; + float t2 = w3[1] - w1[1]; + + if(s1 * t2 - s2 * t1 == 0 && !warningBadTangents) { + warningBadTangents = true; + k3Log(k3_WARN, "Bad tangents. Is mesh properly UV-mapped?"); + } + + float r = 1.f / (s1 * t2 - s2 * t1); + + vec3 sdir = {(t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r}; + vec3 tdir = {(s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r}; + + glm_vec3_add(tans1[inds[i + 0]], sdir, tans1[inds[i + 0]]); + glm_vec3_add(tans1[inds[i + 1]], sdir, tans1[inds[i + 1]]); + glm_vec3_add(tans1[inds[i + 2]], sdir, tans1[inds[i + 2]]); + + glm_vec3_add(tans2[inds[i + 0]], tdir, tans2[inds[i + 0]]); + glm_vec3_add(tans2[inds[i + 1]], tdir, tans2[inds[i + 1]]); + glm_vec3_add(tans2[inds[i + 2]], tdir, tans2[inds[i + 2]]); + } + + int8_t *ret = malloc(verts * 3); + memset(ret, 0, verts * 3); + + for(size_t i = 0; i < verts; i++) { + vec3 n = {nrm[i * 3 + 0], nrm[i * 3 + 1], nrm[i * 3 + 2]}; + glm_vec3_normalize(n); + glm_vec3_scale(n, glm_vec3_dot(n, tans1[i]), n); + + vec3 t; + glm_vec3_sub(tans1[i], n, t); + glm_vec3_normalize(t); + + ret[i * 3 + 0] = t[0] * 127; + ret[i * 3 + 1] = t[1] * 127; + ret[i * 3 + 2] = t[2] * 127; + } + + return ret; +} + +struct k3Mdl *k3MdlCreate(size_t verts, size_t indices, size_t boneCount, vec3 *pos, uint8_t *nrm, float *uvs, uint8_t *cols, uint8_t *boneids, uint16_t *boneweights, uint16_t *inds, mat4 *invBind, uint8_t *boneParents) { + struct k3Mdl *ret = calloc(1, sizeof(*ret)); + ret->verts = verts; + ret->meshCount = 0; + ret->meshes = NULL; + ret->boneCount = boneCount; + ret->invBind = invBind; + ret->boneParents = boneParents; + ret->animCount = 0; + ret->anims = NULL; + + if(k3CPUSkinning && pos && boneids && boneweights) { + ret->cpuSkinning.pos = malloc(sizeof(*ret->cpuSkinning.pos) * verts); + memcpy(ret->cpuSkinning.pos, pos, sizeof(*ret->cpuSkinning.pos) * verts); + + ret->cpuSkinning.boneids = malloc(sizeof(*ret->cpuSkinning.boneids) * verts * 4); + memcpy(ret->cpuSkinning.boneids, boneids, sizeof(*ret->cpuSkinning.boneids) * verts * 4); + + ret->cpuSkinning.boneweights = malloc(sizeof(*ret->cpuSkinning.boneweights) * verts * 4); + memcpy(ret->cpuSkinning.boneweights, boneweights, sizeof(*ret->cpuSkinning.boneweights) * verts * 4); + } + + ret->vstore = k3StorageBasic(); + ret->estore = k3StorageBasic(); + + glBindBufferARB(GL_ARRAY_BUFFER_ARB, ret->vstore->gl); + + size_t size = verts * (12 + 3 + 8 + 3); + if(cols) { + size += verts * 4; + } + if(boneCount) { + size += verts * (4 + 8); + } + + glBufferDataARB(GL_ARRAY_BUFFER_ARB, size, NULL, GL_STREAM_DRAW_ARB); + + int o = 0; + + ret->offV = o; + glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, o, 12 * verts, pos); + o += 12 * verts; + + if(nrm) { + ret->offN = o; + glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, o, 3 * verts, nrm); + o += 3 * verts; + } else ret->offN = -1; + + if(uvs) { + ret->offU = o; + glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, o, 8 * verts, uvs); + o += 8 * verts; + } else ret->offU = -1; + + if(nrm && uvs) { + ret->offT = o; + + int8_t *tans = generate_tangents(verts, indices, inds, pos, nrm, uvs); + glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, o, 3 * verts, tans); + free(tans); + + o += 3 * verts; + } else ret->offT = -1; + + if(cols) { + ret->offC = o; + + glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, o, 4 * verts, cols); + o += 4 * verts; + } else ret->offC = -1; + + if(boneCount) { + ret->offB = o; + + glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, o, 4 * verts, boneids); + o += 4 * verts; + + glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, o, 8 * verts, boneweights); + o += 8 * verts; + } else ret->offB = -1; + + glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, ret->estore->gl); + glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indices * sizeof(uint16_t), inds, GL_STATIC_DRAW_ARB); + + update_aabb(ret, pos); + + ret->debugname = strdup("Unnamed"); + + return ret; +} + +void k3MdlUpdatePos(struct k3Mdl *mdl, vec3 *pos) { + if(mdl->offV == -1) { + k3Log(k3_ERR, "Missing vertex position attribute. Cannot update."); + return; + } + + update_aabb(mdl, pos); + + glBindBufferARB(GL_ARRAY_BUFFER_ARB, mdl->vstore->gl); + glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, mdl->offV, 12 * mdl->verts, pos); +} + +void k3MdlUpdateNrm(struct k3Mdl *mdl, uint8_t *nrm) { + if(mdl->offN == -1) { + k3Log(k3_ERR, "Missing vertex normal attribute. Cannot update."); + return; + } + + glBindBufferARB(GL_ARRAY_BUFFER_ARB, mdl->vstore->gl); + glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, mdl->offN, 3 * mdl->verts, nrm); +} + +void k3MdlAddMesh(struct k3Mdl *mdl, struct k3Mat *mat, uint16_t idxStart, uint16_t idxNumber) { + mdl->meshes = realloc(mdl->meshes, sizeof(*mdl->meshes) * (mdl->meshCount + 1)); + struct k3Mesh *mehs = &mdl->meshes[mdl->meshCount++]; + mehs->idxStart = idxStart; + mehs->idxNumber = idxNumber; + memcpy(&mehs->mat, mat, sizeof(*mat)); +} + +struct k3Mesh *k3MdlGetMeshes(struct k3Mdl *mdl, size_t *count) { + *count = mdl->meshCount; + return mdl->meshes; +} + +void k3MdlAddAnim(struct k3Mdl *mdl, struct k3Animation *anim) { + mdl->anims = realloc(mdl->anims, sizeof(*mdl->anims) * (mdl->animCount + 1)); + mdl->anims[mdl->animCount++] = anim; +} + +struct k3Animation *k3MdlGetAnim(struct k3Mdl *mdl, uint16_t id) { + for(size_t i = 0; i < mdl->animCount; i++) { + if(mdl->anims[i]->id == id) { + return mdl->anims[i]; + } + } +} + +size_t k3MdlGetBoneCount(struct k3Mdl *mdl) { + return mdl->boneCount; +} + +struct k3Mdl *k3MdlCopySubs(struct k3Mdl *src) { + struct k3Mdl *dst = calloc(1, sizeof(*dst)); + + dst->cpuSkinning.pos = src->cpuSkinning.pos; + dst->cpuSkinning.boneids = src->cpuSkinning.boneids; + dst->cpuSkinning.boneweights = src->cpuSkinning.boneweights; + + dst->verts = src->verts; + + dst->vstore = src->vstore; + dst->estore = src->estore; + k3StorageRef((void*) dst->vstore); + k3StorageRef((void*) dst->estore); + + dst->meshCount = src->meshCount; + dst->meshes = malloc(sizeof(*dst->meshes) * dst->meshCount); + memcpy(dst->meshes, src->meshes, sizeof(*dst->meshes) * dst->meshCount); + + dst->boneCount = src->boneCount; + dst->invBind = malloc(sizeof(*dst->invBind) * dst->boneCount); + memcpy(dst->invBind, src->invBind, sizeof(dst->invBind) * dst->boneCount); + dst->boneParents = malloc(sizeof(*dst->boneParents) * dst->boneCount); + memcpy(dst->boneParents, src->boneParents, sizeof(*dst->boneParents) * dst->boneCount); + + dst->animCount = src->animCount; + dst->anims = malloc(sizeof(*dst->anims) * dst->animCount); + memcpy(dst->anims, src->anims, sizeof(*dst->anims) * dst->animCount); + + memcpy(dst->aabb, src->aabb, sizeof(dst->aabb)); + + dst->offB = src->offB; + dst->offC = src->offC; + dst->offN = src->offN; + dst->offT = src->offT; + dst->offU = src->offU; + dst->offV = src->offV; + + dst->debugname = strdup(src->debugname); + + return dst; +} + +void k3MdlSetDebugName(struct k3Mdl *mdl, const char *name) { + free(mdl->debugname); + mdl->debugname = strdup(name); + + k3Log(k3_DEBUG, "Model name: %s", name); +} + +static void anim_update(struct k3Animator *anim) { + float frame = anim->time * anim->base->fps; + + size_t f0 = floorf(frame); + size_t f1 = (f0 + 1); + + float alpha = frame - f0; + + f0 %= anim->base->frameCount; + f1 %= anim->base->frameCount; + + for(size_t b = 0; b < anim->base->boneCount; b++) { + vec4 t0, t1; + glm_vec4_copy(anim->base->frames[anim->base->boneCount * f0 + b].translation, t0); + glm_vec4_copy(anim->base->frames[anim->base->boneCount * f1 + b].translation, t1); + + versor r0, r1; + glm_quat_copy(anim->base->frames[anim->base->boneCount * f0 + b].rotation, r0); + glm_quat_copy(anim->base->frames[anim->base->boneCount * f1 + b].rotation, r1); + + vec4 t; + glm_vec4_lerp(t0, t1, alpha, t); + + versor r; + glm_quat_slerp(r0, r1, alpha, r); + + mat4 m = GLM_MAT4_IDENTITY_INIT; + glm_quat_mat4(r, m); + glm_vec4_copy(t, m[3]); + + uint8_t parent = anim->base->boneParents[b]; + if(parent != 0xFF) { + glm_mat4_mul(anim->inter[parent], m, anim->inter[b]); + } else { + glm_mat4_copy(m, anim->inter[b]); + } + } + + for(size_t b = 0; b < anim->base->boneCount; b++) { + glm_mat4_mul(anim->inter[b], anim->base->invBind[b], anim->inter[b]); + } +} +void k3AnimatorSet(struct k3Animator *anim, float time) { + if(!anim->inter) anim->inter = _mm_malloc(sizeof(mat4) * anim->base->boneCount * anim->base->frameCount, 16); + + anim->time = time; + + anim_update(anim); +} +void k3AnimatorStep(struct k3Animator *anim, float dt) { + k3AnimatorSet(anim, anim->time + dt); +} + + +struct k3Tex *k3TexCreate(enum k3TexType type) { + struct k3Tex *ret = calloc(1, sizeof(*ret)); + + if(type == k3_CUBEMAP) { + ret->cubemap = 1; + } + + return ret; +} + +void k3TexUpdate(struct k3Tex *tex, enum k3TexType type, int index, uint16_t width, uint16_t height, void *data) { + GLenum target = tex->cubemap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D; + + /* + The Intel 4500MHD driver causes broken textures upon calling glTexImage2D a second time to resize them. + Specifically, the textures appear to be read as if all zeroes. + And they return to normal once you come back to the original size!?! + + For this reason, each k3TexUpdate call should destroy the texture name, and create a new one. + */ + if(tex->tex) { + // Assume cubemaps don't get reuploaded because I'm lazy.. + if(!tex->cubemap) { + glDeleteTextures(1, &tex->tex); + tex->tex = 0; + } + } else { + k3Log(k3_INFO, "Init type %i tex (%u,%u,%u)", type, width, height, 0); + } + + GLint internalFmt, externalFmt, intype; + int linearFilter, mipmap; + + switch(type) { + case k3_RAWCOLOR: + internalFmt = GLAD_GL_ARB_texture_float ? GL_RGBA32F_ARB : GL_RGB10_A2; + externalFmt = GL_RGBA; + intype = GL_UNSIGNED_BYTE; + linearFilter = 1; + mipmap = 1; + break; + case k3_NORMAL: + internalFmt = GL_RGBA; + externalFmt = GL_RGBA; + intype = GL_UNSIGNED_BYTE; + linearFilter = 1; + mipmap = 1; + break; + case k3_DIFFUSE: + case k3_EMISSION: + internalFmt = GLAD_GL_EXT_texture_compression_s3tc + ? (GLAD_GL_EXT_texture_sRGB ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT : GL_COMPRESSED_RGBA_S3TC_DXT5_EXT) + : (GLAD_GL_EXT_texture_sRGB ? GL_SRGB8_ALPHA8_EXT : GL_RGBA8); + externalFmt = GL_RGBA; + intype = GL_UNSIGNED_BYTE; + linearFilter = 1; + mipmap = 1; + break; + case k3_DISPLACEMENT: + case k3_ROUGHNESS: + internalFmt = !k3IsCore ? GL_LUMINANCE8 : GL_RED; + externalFmt = GL_RED; + intype = GL_UNSIGNED_BYTE; + linearFilter = 1; + mipmap = 1; + break; + case k3_ALPHA: + internalFmt = !k3IsCore ? GL_ALPHA8 : GL_RED; + externalFmt = !k3IsCore ? GL_ALPHA : GL_RED; + intype = GL_UNSIGNED_BYTE; + linearFilter = 1; + mipmap = 1; + break; + case k3_DEPTH: + internalFmt = GL_DEPTH_COMPONENT24; + externalFmt = GL_DEPTH_COMPONENT; + intype = GL_UNSIGNED_INT; + if(!k3IsCore) glTexParameteri(target, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE); + linearFilter = 1; + mipmap = 0; + break; + default: + abort(); + break; + } + + if(!tex->tex) { + glGenTextures(1, &tex->tex); + glBindTexture(target, tex->tex); + + if(tex->cubemap) { + glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + if(!k3IsCore && !mipmap) { + glTexParameteri(target, GL_GENERATE_MIPMAP, GL_FALSE); + } else { + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + + if(type == k3_DEPTH) { + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, (const float[]) {1, 1, 1, 1}); + glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + } + } else { + glBindTexture(target, tex->tex); + } + + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, linearFilter ? GL_LINEAR : GL_NEAREST); + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, linearFilter ? GL_LINEAR : GL_NEAREST); + + if(k3IsCore && type == k3_ALPHA) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_ONE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_ONE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_ONE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_RED); + } + + if(tex->cubemap) { + target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + index; + } else if(index) { + k3Log(k3_WARN, "Non-zero index passed for non-cubemap texture"); + } + + glTexImage2D(target, 0, internalFmt, width, height, 0, externalFmt, intype, data); + + if(!tex->cubemap && k3IsCore && mipmap) { + glGenerateMipmap(target); + } + + tex->szX = width; + tex->szY = height; + tex->szZ = 0; +} +uint32_t k3TexSzX(struct k3Tex *this) { + return this->szX; +} +uint32_t k3TexSzY(struct k3Tex *this) { + return this->szY; +} +uint32_t k3TexSzZ(struct k3Tex *this) { + return this->szZ; +} + +static size_t LightCountOld; +static size_t LightCount; +static struct k3Light *Lights; +void k3SetLights(size_t count, struct k3Light *lightsNew) { + LightCount = count; + Lights = lightsNew; +} + +struct k3Light *k3GetLights(size_t *a) { + *a = LightCount; + return Lights; +} + +void k3Clear() { + glDepthMask(GL_TRUE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + glClearColor(0.2, 0.2, 0.2, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +uint8_t k3GraphicalReduction; + +struct Renderable { + mat4 modelmat; + struct k3Mdl *mdl; + struct k3Mesh *mesh; + struct k3AnimationBone *bones; + GLhandleARB glsl; + GLuint arbvp; + GLuint arbfp; +} __attribute__((aligned(16))); +static struct Renderable *renderQueue = NULL; +static size_t renderQueueSize, renderQueueCapacity = 0; + +static mat4 CamMat; +static mat4 ProjMat; + +struct LightShadow { + mat4 vp; + vec4 atlasSegment; +#ifdef k3_IRREGULAR_SHADOWS + GLuint multimapEls; // buffer + GLuint multimapHeads; // image +#endif +} __attribute__((aligned(16))); +static size_t LightShadowsCount, LightShadowsCapacity; +static struct LightShadow *LightShadows; +static struct k3Offscreen *ShadowAtlas; + +static bool LightShadowIrregularMode = false; +static GLuint IrregPixelsInShadow; + +static intmax_t rblecompar1(const void *a, const void *b) { + int i = ((const struct Renderable*) a)->mesh->mat.passes[0].transparent - ((const struct Renderable*) b)->mesh->mat.passes[0].transparent; + + if(i == 0) { + float dist2A = glm_vec3_distance2(((const struct Renderable*) a)->modelmat[3], CamMat[3]); + float dist2B = glm_vec3_distance2(((const struct Renderable*) b)->modelmat[3], CamMat[3]); + i = copysignf(1, dist2A - dist2B); + if(((const struct Renderable*) a)->mesh->mat.passes[0].transparent) { + i = -i; + } + } + + if(i == 0) { + i = ((const struct Renderable*) a)->glsl - ((const struct Renderable*) b)->glsl; + } + + if(i == 0) { + i = ((const struct Renderable*) a)->arbfp - ((const struct Renderable*) b)->arbfp; + } + + if(i == 0) { + i = ((const struct Renderable*) a)->arbvp - ((const struct Renderable*) b)->arbvp; + } + + //if(i == 0) { + // i = (uintptr_t) ((const struct Renderable*) a)->mesh->mat - (uintptr_t) ((const struct Renderable*) b)->mesh->mat; + //} + + return i; +} +static void queuesort() { + ssort(renderQueue, renderQueueSize, sizeof(struct Renderable), rblecompar1); +} + +void k3Batch(struct k3Mdl *mdl, mat4 modelmat, struct k3AnimationBone *bones) { + for(size_t mesh = 0; mesh < mdl->meshCount; mesh++) { + if(renderQueueSize == renderQueueCapacity) { + struct Renderable *new = _mm_malloc(sizeof(*renderQueue) * (renderQueueCapacity + 64), 16); + if(renderQueue) { + memcpy(new, renderQueue, sizeof(*renderQueue) * renderQueueCapacity); + _mm_free(renderQueue); + } + renderQueue = new; + renderQueueCapacity += 64; + } + struct Renderable *r = &renderQueue[renderQueueSize++]; + r->mdl = mdl; + r->mesh = &mdl->meshes[mesh]; + glm_mat4_copy(modelmat, r->modelmat); + r->bones = bones; + r->glsl = GL_FROM_K3GLSL(r->mesh->mat.passes[0].glsl.hp); + r->arbvp = GL_FROM_K3ARBVP(r->mesh->mat.passes[0].arbvp.vp); + r->arbfp = GL_FROM_K3ARBFP(r->mesh->mat.passes[0].arbfp.fp); + } +} + +static void setup_ff_projection(mat4 proj) { + if(!k3IsCore) { + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glLoadMatrixf((float*) proj); + } +} + +static void setup_core_projection(GLuint prog, mat4 proj) { + if(k3IsCore) { + GLint u = glGetUniformLocation(prog, "u_projection"); + + if(u != -1) { + glUniformMatrix4fv(u, 1, GL_FALSE, (float*) proj); + } + } +} + +static void setup_ff_lights(mat4 view, int lightStart, int lightCount) { + // Some drivers crash on glEnable(GL_LIGHTING) *AND* glDisable(GL_LIGHTING) for some reason + // Just don't do it unless fixed-function is explicitly on + + if(!k3IsCore) { + + static int lightCountOld = 0; + if(lightCountOld != lightCount) { + if(lightCount > lightCountOld) { + for(int i = lightCountOld; i < lightCount; i++) { + glEnable(GL_LIGHT0 + i); + } + } else { + for(int i = lightCount; i < lightCountOld; i++) { + glDisable(GL_LIGHT0 + i); + } + } + + lightCountOld = lightCount; + } + + glMatrixMode(GL_MODELVIEW); + glLoadMatrixf((float*) view); // Lighting in eye-space + if(LightCount) + glEnable(GL_LIGHTING); + else + glDisable(GL_LIGHTING); + + for(int i = 0; i < lightCount; i++) { + glLightfv(GL_LIGHT0 + i, GL_POSITION, Lights[lightStart + i].dir.direction); + glLightfv(GL_LIGHT0 + i, GL_DIFFUSE, Lights[lightStart + i].color); + + // Fixed-function does not support a light cutoff radius, so approximate, assuming 0.01 is black + glLightf(GL_LIGHT0 + i, GL_CONSTANT_ATTENUATION, 1); + glLightf(GL_LIGHT0 + i, GL_LINEAR_ATTENUATION, 0); + glLightf(GL_LIGHT0 + i, GL_QUADRATIC_ATTENUATION, 1.f / (Lights[lightStart + i].radius * Lights[lightStart + i].radius * 0.01)); + } + + } +} + +static void setup_arbprog_globals() { + if(GLAD_GL_ARB_fragment_program) { + glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, Time, 0, 0, 0); + } + + if(GLAD_GL_ARB_vertex_program) { + glProgramEnvParameter4fARB(GL_VERTEX_PROGRAM_ARB, 0, Time, 0, 0, 0); + } +} + +static void setup_glsl_globals(GLuint bound, mat4 view) { + if(!k3IsCore) { + glUniform1fARB(glGetUniformLocationARB(bound, "u_time"), Time); + glUniform3fARB(glGetUniformLocationARB(bound, "u_cam"), CamMat[3][0], CamMat[3][1], CamMat[3][2]); + glUniformMatrix4fvARB(glGetUniformLocationARB(bound, "u_view"), 1, GL_FALSE, (float*) view); + } else { + glUniform1f(glGetUniformLocationARB(bound, "u_time"), Time); + glUniform3f(glGetUniformLocationARB(bound, "u_cam"), CamMat[3][0], CamMat[3][1], CamMat[3][2]); + glUniformMatrix4fv(glGetUniformLocationARB(bound, "u_view"), 1, GL_FALSE, (float*) view); + } +} + +static void setup_glsl_mat_uniforms(GLhandleARB bound, struct k3Mat *mat, int pass) { + for(int u = 0; u < mat->passes[pass].glsl.uCount; u++) { + if(mat->passes[pass].glsl.u[u].type == k3_MAT_UNIFORM_I1) { + GLuint id = glGetUniformLocationARB(bound, mat->passes[pass].glsl.u[u].name); + + if(!k3IsCore) { + glUniform1iARB(id, mat->passes[pass].glsl.u[u].i1); + } else { + glUniform1i(id, mat->passes[pass].glsl.u[u].i1); + } + } else if(mat->passes[pass].glsl.u[u].type == k3_MAT_UNIFORM_F1) { + GLuint id = glGetUniformLocationARB(bound, mat->passes[pass].glsl.u[u].name); + + if(!k3IsCore) { + glUniform1fARB(id, mat->passes[pass].glsl.u[u].f1); + } else { + glUniform1f(id, mat->passes[pass].glsl.u[u].f1); + } + } + } +} + +static void setup_glsl_model_uniforms(GLuint bound, float *modelmat) { + mat4 invmodel; + glm_mat4_inv(modelmat, invmodel); + + if(!k3IsCore) { + glUniformMatrix4fvARB(glGetUniformLocationARB(bound, "u_model"), 1, GL_FALSE, (float*) modelmat); + glUniformMatrix4fvARB(glGetUniformLocationARB(bound, "u_imodel"), 1, GL_FALSE, (float*) invmodel); + } else { + glUniformMatrix4fv(glGetUniformLocationARB(bound, "u_model"), 1, GL_FALSE, (float*) modelmat); + glUniformMatrix4fv(glGetUniformLocationARB(bound, "u_imodel"), 1, GL_FALSE, (float*) invmodel); + } +} + +static void setup_glsl_lighting_uniforms(GLuint bound, int lightsStart, int lightsCount) { + if(lightsCount > 4) { + lightsCount = 4; + k3Log(k3_ERR, "Max 4 lights per pass"); + } + + if(lightsStart >= LightCount) { + lightsStart = 0; + lightsCount = 0; + } + + if(lightsStart + lightsCount > LightCount) { + lightsCount = LightCount - lightsStart; + } + + vec4 settings1[4]; + vec4 settings2[4]; + + vec4 colors[4]; + memset(colors, 0, sizeof(colors)); + + for(int i = 0; i < lightsCount; i++) { + struct k3Light *l = &Lights[i + lightsStart]; + + if(l->type == k3_DIRECTIONAL) { + glm_vec4_copy(l->dir.direction, settings1[i]); + } else if(l->type == k3_SPOT) { + glm_vec4_copy(l->spot.position, settings1[i]); + + float inclination = acosf(l->spot.direction[1]); + float azimuth = atan2f(l->spot.direction[2], l->spot.direction[0]); + + settings2[i][0] = inclination; + settings2[i][1] = azimuth; + settings2[i][2] = cosf(l->spot.angle); + settings2[i][3] = l->radius; + } else if(l->type == k3_HALF_OMNI) { + glm_vec4_copy(l->omni.position, settings1[i]); + settings1[i][3] = 2; + + settings2[i][3] = l->radius; + } else if(l->type == k3_OMNI) { + glm_vec4_copy(l->omni.position, settings1[i]); + settings1[i][3] = 3; + + settings2[i][3] = l->radius; + } + + memcpy(colors[i], Lights[lightsStart + i].color, sizeof(vec4)); + } + + vec4 ambient = {0, 0, 0, 0}; + + if(lightsStart == 0) { + glm_vec4_copy((vec4) {0.018, 0.018, 0.040, 0}, ambient); + } + + if(!k3IsCore) { + glUniform4fvARB(glGetUniformLocationARB(bound, "u_BaseLightSettings1"), 4, (float*) settings1); + glUniform4fvARB(glGetUniformLocationARB(bound, "u_BaseLightSettings2"), 4, (float*) settings2); + glUniform4fvARB(glGetUniformLocationARB(bound, "u_BaseLightColors"), 4, (float*) colors); + glUniform4fvARB(glGetUniformLocationARB(bound, "u_AmbientLight"), 1, (float*) ambient); + } else { + glUniform4fv(glGetUniformLocationARB(bound, "u_BaseLightSettings1"), 4, (float*) settings1); + glUniform4fv(glGetUniformLocationARB(bound, "u_BaseLightSettings2"), 4, (float*) settings2); + glUniform4fv(glGetUniformLocationARB(bound, "u_BaseLightColors"), 4, (float*) colors); + glUniform4fv(glGetUniformLocationARB(bound, "u_AmbientLight"), 1, (float*) ambient); + } +} + +static void setup_glsl_shadow_uniforms(GLuint bound, int atlasUnit, int lightsStart, int lightsCount) { + if(lightsCount > 4) { + lightsCount = 4; + k3Log(k3_ERR, "Max 4 lights per pass"); + } + + if(!LightShadows) return; + + if(LightShadowIrregularMode) { + assert(k3IsCore); + + glUniform1i(glGetUniformLocation(bound, "u_pixelsinshadow"), 0); + } else { + mat4 m[4]; + memset(m, 0, sizeof(m)); + + vec4 seg[4]; + + for(int i = 0; i < lightsCount; i++) { + glm_mat4_copy(LightShadows[i + lightsStart].vp, m[i]); + glm_vec4_copy(LightShadows[i + lightsStart].atlasSegment, seg[i]); + } + + // Make it look outside the atlas completely + for(int i = lightsCount; i < 4; i++) { + seg[i][0] = -1; + seg[i][1] = -1; + seg[i][2] = -1; + seg[i][3] = -1; + } + + if(!k3IsCore) { + glUniformMatrix4fvARB(glGetUniformLocationARB(bound, "u_shadows0vp"), 4, GL_FALSE, (float*) m); + glUniform4fvARB(glGetUniformLocationARB(bound, "u_shadows0seg"), 4, (float*) seg); + + glUniform1iARB(glGetUniformLocationARB(bound, "u_shadows0atlas"), atlasUnit); + } else { + glUniformMatrix4fv(glGetUniformLocationARB(bound, "u_shadows0vp"), 4, GL_FALSE, (float*) m); + glUniform4fv(glGetUniformLocationARB(bound, "u_shadows0seg"), 4, (float*) seg); + + glUniform1i(glGetUniformLocationARB(bound, "u_shadows0atlas"), atlasUnit); + } + } +} + +static int bind_mat_textures(struct k3Mat *mat, int pass) { + int i; + if(GLAD_GL_EXT_direct_state_access) { + for(i = 0; i < k3_MAX_GLSL_UNITS; i++) { + glBindMultiTextureEXT(GL_TEXTURE0 + i, GL_TEXTURE_2D, GL_FROM_K3TEX(mat->passes[pass].units[i])); + } + } else { + for(i = 0; i < k3_MAX_GLSL_UNITS; i++) { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, GL_FROM_K3TEX(mat->passes[pass].units[i])); + } + } + return i; +} + +static int bind_shadow_texture(int textureUnit) { + if(LightShadowIrregularMode) { + if(glBindImageTexture) { + glBindImageTexture(0, IrregPixelsInShadow, 0, GL_FALSE, 0, GL_READ_WRITE_ARB, GL_R32UI); + } else { + glBindImageTextureEXT(0, IrregPixelsInShadow, 0, GL_FALSE, 0, GL_READ_WRITE_ARB, GL_R32UI); + } + return textureUnit; + } else { + if(GLAD_GL_EXT_direct_state_access) { + glBindMultiTextureEXT(GL_TEXTURE0 + textureUnit, GL_TEXTURE_2D, GL_FROM_K3TEX(ShadowAtlas->depth)); + } else { + glActiveTexture(GL_TEXTURE0 + textureUnit); + glBindTexture(GL_TEXTURE_2D, GL_FROM_K3TEX(ShadowAtlas->depth)); + } + return ++textureUnit; + } +} + +static void enable_glsl_bones(GLuint bound, struct k3Mdl *mdl, struct k3AnimationBone *bones) { + if(mdl->boneCount) { + GLint a0; + GLint a1; + + if(!k3IsCore) { + a0 = glGetAttribLocationARB(bound, "a_boneids"); + a1 = glGetAttribLocationARB(bound, "a_boneweights"); + } else { + a0 = glGetAttribLocation(bound, "a_boneids"); + a1 = glGetAttribLocation(bound, "a_boneweights"); + } + + if((a0 == -1) != (a1 == -1)) { + k3Log(k3_ERR, "a_boneids and a_boneweights must be both null or non-null"); + } + + if(a0 != -1) { + if(!k3IsCore) { + glEnableVertexAttribArrayARB(a0); + glEnableVertexAttribArrayARB(a1); + glVertexAttribPointerARB(a0, 4, GL_UNSIGNED_BYTE, GL_FALSE, 0, (void*) (mdl->offB + 0 * mdl->verts)); + glVertexAttribPointerARB(a1, 4, GL_UNSIGNED_SHORT, GL_TRUE, 0, (void*) (mdl->offB + 4 * mdl->verts)); + } else { + glEnableVertexAttribArray(a0); + glEnableVertexAttribArray(a1); + glVertexAttribPointer(a0, 4, GL_UNSIGNED_BYTE, GL_FALSE, 0, (void*) (mdl->offB + 0 * mdl->verts)); + glVertexAttribPointer(a1, 4, GL_UNSIGNED_SHORT, GL_TRUE, 0, (void*) (mdl->offB + 4 * mdl->verts)); + } + } + + if(bones) { + if(!k3IsCore) { + glUniform4fvARB(glGetUniformLocationARB(bound, "u_bonedata"), 2 * mdl->boneCount, (float*) bones); + } else { + glUniform4fv(glGetUniformLocationARB(bound, "u_bonedata"), 2 * mdl->boneCount, (float*) bones); + } + } else { + vec4 data[48] = {}; + for(int i = 0; i < 48; i++) { + data[i][3] = 1; + } + + if(!k3IsCore) { + glUniform4fvARB(glGetUniformLocationARB(bound, "u_bonedata"), 48, (float*) data); + } else { + glUniform4fv(glGetUniformLocationARB(bound, "u_bonedata"), 48, (float*) data); + } + } + } +} + +static void disable_glsl_bones(struct k3Mdl *mdl, GLuint bound) { + if(mdl->boneCount) { + GLint a0; + GLint a1; + + a0 = glGetAttribLocationARB(bound, "a_boneids"); + a1 = glGetAttribLocationARB(bound, "a_boneweights"); + + if(a0 != -1) { + if(!k3IsCore) { + glDisableVertexAttribArrayARB(a0); + glDisableVertexAttribArrayARB(a1); + } else { + glDisableVertexAttribArray(a0); + glDisableVertexAttribArray(a1); + } + } + } +} + +static void enable_glsl_tangents(GLuint bound, struct k3Mdl *mdl) { + if(mdl->offT == -1) { + return; + } + + GLint a; + + if(!k3IsCore) { + a = glGetAttribLocationARB(bound, "a_tangent"); + } else { + a = glGetAttribLocation(bound, "a_tangent"); + } + + if(a == -1) { + return; + } + + if(!k3IsCore) { + glEnableVertexAttribArrayARB(a); + glVertexAttribPointerARB(a, 3, GL_BYTE, GL_TRUE, 0, (void*) mdl->offT); + } else { + glEnableVertexAttribArray(a); + glVertexAttribPointer(a, 3, GL_BYTE, GL_TRUE, 0, (void*) mdl->offT); + } +} + +static void disable_glsl_tangents(GLuint bound, struct k3Mdl *mdl) { + if(mdl->offT == -1) { + return; + } + + GLint a; + + if(!k3IsCore) { + a = glGetAttribLocationARB(bound, "a_tangent"); + } else { + a = glGetAttribLocation(bound, "a_tangent"); + } + + if(a == -1) { + return; + } + + if(!k3IsCore) { + glDisableVertexAttribArrayARB(a); + } else { + glDisableVertexAttribArray(a); + } +} + +static void enable_vertex_buffers(struct k3Mdl *mdl, GLuint prog) { + if(!k3IsCore) { + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glVertexPointer(3, GL_FLOAT, 0, (void*) (uintptr_t) mdl->offV); + glNormalPointer(GL_BYTE, 0, (void*) (uintptr_t) mdl->offN); + glTexCoordPointer(2, GL_FLOAT, 0, (void*) (uintptr_t) mdl->offU); + + if(mdl->offC != -1) { + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(4, GL_UNSIGNED_BYTE, 0, (void*) (uintptr_t) mdl->offC); + } else { + glColor4f(1, 1, 1, 1); + } + } else if(prog) { + GLint aPos = glGetAttribLocation(prog, "a_pos"); + GLint aNormal = glGetAttribLocation(prog, "a_normal"); + GLint aUv = glGetAttribLocation(prog, "a_uv"); + GLint aColor = glGetAttribLocation(prog, "a_color"); + + if(aPos != -1) { + glEnableVertexAttribArray(aPos); + glVertexAttribPointer(aPos, 3, GL_FLOAT, GL_FALSE, 0, (void*) (uintptr_t) mdl->offV); + } + + if(aNormal != -1) { + glEnableVertexAttribArray(aNormal); + glVertexAttribPointer(aNormal, 3, GL_BYTE, GL_TRUE, 0, (void*) (uintptr_t) mdl->offN); + } + + if(aUv != -1) { + glEnableVertexAttribArray(aUv); + glVertexAttribPointer(aUv, 2, GL_FLOAT, GL_FALSE, 0, (void*) (uintptr_t) mdl->offU); + } + + if(mdl->offC != -1 && aColor != -1) { + glEnableVertexAttribArray(aColor); + glVertexAttribPointer(aColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, (void*) (uintptr_t) mdl->offC); + } else if(aColor != -1) { + glVertexAttrib4f(aColor, 1, 1, 1, 1); + } + } +} + +static void disable_vertex_buffers(struct k3Mdl *mdl, GLuint prog) { + if(!k3IsCore) { + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + if(mdl->offC != -1) { + glDisableClientState(GL_COLOR_ARRAY); + } + } else if(prog) { + GLint aPos = glGetAttribLocation(prog, "a_pos"); + GLint aNormal = glGetAttribLocation(prog, "a_normal"); + GLint aUv = glGetAttribLocation(prog, "a_uv"); + GLint aColor = glGetAttribLocation(prog, "a_color"); + + if(aPos != -1) { + glDisableVertexAttribArray(aPos); + } + + if(aNormal != -1) { + glDisableVertexAttribArray(aNormal); + } + + if(aUv != -1) { + glDisableVertexAttribArray(aUv); + } + + if(mdl->offC != -1 && aColor != -1) { + glDisableVertexAttribArray(aColor); + } + } +} + +static void push_aabb(struct k3Mat *mat, int pass, struct k3Mdl *mdl) { + float aabb = mat->passes[pass].aabb; + + vec3 verts[] = { + -aabb + mdl->aabb[0][0], -aabb + mdl->aabb[0][1], -aabb + mdl->aabb[0][2], + -aabb + mdl->aabb[0][0], +aabb + mdl->aabb[1][1], -aabb + mdl->aabb[0][2], + +aabb + mdl->aabb[1][0], -aabb + mdl->aabb[0][1], -aabb + mdl->aabb[0][2], + + -aabb + mdl->aabb[0][0], +aabb + mdl->aabb[1][1], -aabb + mdl->aabb[0][2], + +aabb + mdl->aabb[1][0], +aabb + mdl->aabb[1][1], -aabb + mdl->aabb[0][2], + +aabb + mdl->aabb[1][0], -aabb + mdl->aabb[0][1], -aabb + mdl->aabb[0][2], + + -aabb + mdl->aabb[0][0], -aabb + mdl->aabb[0][1], +aabb + mdl->aabb[1][2], + +aabb + mdl->aabb[1][0], -aabb + mdl->aabb[0][1], +aabb + mdl->aabb[1][2], + -aabb + mdl->aabb[0][0], +aabb + mdl->aabb[1][1], +aabb + mdl->aabb[1][2], + + +aabb + mdl->aabb[1][0], -aabb + mdl->aabb[0][1], +aabb + mdl->aabb[1][2], + +aabb + mdl->aabb[1][0], +aabb + mdl->aabb[1][1], +aabb + mdl->aabb[1][2], + -aabb + mdl->aabb[0][0], +aabb + mdl->aabb[1][1], +aabb + mdl->aabb[1][2], + + -aabb + mdl->aabb[0][0], -aabb + mdl->aabb[0][1], +aabb + mdl->aabb[1][2], + -aabb + mdl->aabb[0][0], +aabb + mdl->aabb[1][1], +aabb + mdl->aabb[1][2], + -aabb + mdl->aabb[0][0], -aabb + mdl->aabb[0][1], -aabb + mdl->aabb[0][2], + + -aabb + mdl->aabb[0][0], +aabb + mdl->aabb[1][1], +aabb + mdl->aabb[1][2], + -aabb + mdl->aabb[0][0], +aabb + mdl->aabb[1][1], -aabb + mdl->aabb[0][2], + -aabb + mdl->aabb[0][0], -aabb + mdl->aabb[0][1], -aabb + mdl->aabb[0][2], + + +aabb + mdl->aabb[1][0], -aabb + mdl->aabb[0][1], +aabb + mdl->aabb[1][2], + +aabb + mdl->aabb[1][0], -aabb + mdl->aabb[0][1], -aabb + mdl->aabb[0][2], + +aabb + mdl->aabb[1][0], +aabb + mdl->aabb[1][1], +aabb + mdl->aabb[1][2], + + +aabb + mdl->aabb[1][0], -aabb + mdl->aabb[0][1], -aabb + mdl->aabb[0][2], + +aabb + mdl->aabb[1][0], +aabb + mdl->aabb[1][1], -aabb + mdl->aabb[0][2], + +aabb + mdl->aabb[1][0], +aabb + mdl->aabb[1][1], +aabb + mdl->aabb[1][2], + + -aabb + mdl->aabb[0][0], +aabb + mdl->aabb[1][1], +aabb + mdl->aabb[1][2], + +aabb + mdl->aabb[1][0], +aabb + mdl->aabb[1][1], +aabb + mdl->aabb[1][2], + -aabb + mdl->aabb[0][0], +aabb + mdl->aabb[1][1], -aabb + mdl->aabb[0][2], + + +aabb + mdl->aabb[1][0], +aabb + mdl->aabb[1][1], +aabb + mdl->aabb[1][2], + +aabb + mdl->aabb[1][0], +aabb + mdl->aabb[1][1], -aabb + mdl->aabb[0][2], + -aabb + mdl->aabb[0][0], +aabb + mdl->aabb[1][1], -aabb + mdl->aabb[0][2], + + -aabb + mdl->aabb[0][0], -aabb + mdl->aabb[0][1], +aabb + mdl->aabb[1][2], + -aabb + mdl->aabb[0][0], -aabb + mdl->aabb[0][1], -aabb + mdl->aabb[0][2], + +aabb + mdl->aabb[1][0], -aabb + mdl->aabb[0][1], +aabb + mdl->aabb[1][2], + + -aabb + mdl->aabb[0][0], -aabb + mdl->aabb[0][1], -aabb + mdl->aabb[0][2], + +aabb + mdl->aabb[1][0], -aabb + mdl->aabb[0][1], -aabb + mdl->aabb[0][2], + +aabb + mdl->aabb[1][0], -aabb + mdl->aabb[0][1], +aabb + mdl->aabb[1][2], + }; + + if(!k3IsCore) { + glBegin(GL_TRIANGLES); + for(int i = 0; i < sizeof(verts) / sizeof(*verts); i++) { + glVertex3f(verts[i][0], verts[i][1], verts[i][2]); + } + glEnd(); + } else { + static GLuint vbo; + if(!vbo) { + glGenBuffers(1, &vbo); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_DYNAMIC_DRAW); + } else { + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts); + } + + GLint aPos = glGetAttribLocation(GL_FROM_K3GLSL(mat->passes[0].glsl.hp), "a_pos"); + + glEnableVertexAttribArray(aPos); + glVertexAttribPointer(aPos, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0); + + glDrawArrays(GL_TRIANGLES, 0, sizeof(verts) / sizeof(*verts)); + + glDisableVertexAttribArray(aPos); + } +} + +static void apply_cpu_skinning(struct k3Mdl *mdl, struct k3AnimationBone *bones) { + if(mdl->boneCount == 0) { + return; + } + + vec3 *boned = alloca(mdl->verts * sizeof(*boned)); + + for(size_t i = 0; i < mdl->verts; i++) { + glm_vec3_zero(boned[i]); + + for(size_t boner = 0; boner < 4; boner++) { + int bone = mdl->cpuSkinning.boneids[i * 4 + boner]; + + float weight = mdl->cpuSkinning.boneweights[i * 4 + boner] / 65535.f; + + vec3 a; + glm_vec3_scale((float*) bones[bone].rotation, 2 * glm_vec3_dot(mdl->cpuSkinning.pos[i], (float*) bones[bone].rotation), a); + + vec3 b; + glm_vec3_scale(mdl->cpuSkinning.pos[i], bones[bone].rotation[3] * bones[bone].rotation[3] - glm_vec3_dot((float*) bones[bone].rotation, (float*) bones[bone].rotation), b); + + vec3 c; + glm_vec3_cross((float*) bones[bone].rotation, mdl->cpuSkinning.pos[i], c); + glm_vec3_scale(c, 2 * bones[bone].rotation[3], c); + + vec3 v; + + glm_vec3_add(a, b, v); + glm_vec3_add(v, c, v); + + glm_vec3_add(v, (float*) bones[bone].translation, v); + + glm_vec3_muladds(v, weight, boned[i]); + } + } + + glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, mdl->offV, mdl->verts * sizeof(*boned), boned); +} + +static void forward_subpass(mat4 view, int transparent, int lightsStart, int lightsCount, size_t rbleStart, size_t rbleEnd) { + setup_ff_lights(view, lightsStart, lightsCount); + + GLhandleARB lastGLSL = -1; + GLuint lastVP = 0, lastFP = 0; + struct k3Mat *lastMaterial = NULL; + int lastAdditive = -1; + + for(size_t rble = rbleStart; rble < rbleEnd; rble++) { + struct k3Mdl *mdl = renderQueue[rble].mdl; + struct k3Mesh *mesh = renderQueue[rble].mesh; + float *modelmat = (float*) renderQueue[rble].modelmat; + struct k3AnimationBone *bones = renderQueue[rble].bones; + GLhandleARB glsl = renderQueue[rble].glsl; + GLuint arbvp = renderQueue[rble].arbvp; + GLuint arbfp = renderQueue[rble].arbfp; + + struct k3Mat *mat = &mesh->mat; + + if(mat->passes[0].additive && lightsStart != 0) { + // Additive materials aren't affected by lighting. Do not draw more than once. + continue; + } + + if(mat->passes[0].transparent != transparent) { + continue; + } + + if(lastAdditive != mat->passes[0].additive) { + if(mat->passes[0].additive) { + glDepthMask(GL_FALSE); + glBlendFuncSeparate(GL_ONE, GL_ONE, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } else { + glDepthMask(GL_TRUE); + + if(transparent) { + if(lightsStart == 0) { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } else { + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + } + } else { + if(lightsStart == 0) { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } else { + glBlendFunc(GL_ONE, GL_ONE); + } + } + } + + lastAdditive = mat->passes[0].additive; + } + + if(glsl) { + if(lastGLSL != glsl) { + if(k3IsCore) + glUseProgram(glsl); + else + glUseProgramObjectARB(glsl); + + lastGLSL = glsl; + + setup_glsl_globals(glsl, view); + } + + setup_core_projection(glsl, ProjMat); + + setup_glsl_mat_uniforms(glsl, mat, 0); + setup_glsl_shadow_uniforms(glsl, mat->passes[0].unitsUsed, lightsStart, lightsCount); + setup_glsl_lighting_uniforms(glsl, lightsStart, lightsCount); + + setup_glsl_model_uniforms(glsl, modelmat); + + if(mat != lastMaterial) { + bind_mat_textures(mat, 0); + bind_shadow_texture(mat->passes[0].unitsUsed); + } + } else if(!k3IsCore) { + if(lastGLSL && GLAD_GL_ARB_shading_language_100) { + glUseProgramObjectARB(0); + } + lastGLSL = 0; + + if(lastVP != arbvp) { + if(lastVP && !arbvp) { + glDisable(GL_VERTEX_PROGRAM_ARB); + } else if(!lastVP && arbvp) { + glEnable(GL_VERTEX_PROGRAM_ARB); + } + + if(arbvp) { + glBindProgramARB(GL_VERTEX_PROGRAM_ARB, arbvp); + } + + lastVP = arbvp; + } + + if(lastFP != arbfp) { + if(lastFP && !arbfp) { + glDisable(GL_FRAGMENT_PROGRAM_ARB); + } else if(!lastFP && arbfp) { + glEnable(GL_FRAGMENT_PROGRAM_ARB); + } + + if(arbfp) { + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, arbfp); + + if(mat != lastMaterial) { + bind_mat_textures(mat, 0); + } + } + + lastFP = arbfp; + } + + if(!k3IsCore && !arbfp) { + glActiveTexture(GL_TEXTURE0); + if(mat->passes[0].units[0]) { + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, GL_FROM_K3TEX(mat->passes[0].units[0])); + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, (float[]) {1, 1, 1, 1}); + } else { + glDisable(GL_TEXTURE_2D); + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, mat->primitive.diffuse); + } + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat->primitive.specular); + glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, mat->primitive.emission); + glMateriali(GL_FRONT_AND_BACK, GL_SHININESS, mat->primitive.shininess * 128); + } + } + + lastMaterial = mat; + + if(mat->passes[0].nocull) { + glDisable(GL_CULL_FACE); + } else { + glEnable(GL_CULL_FACE); + } + if(!k3IsCore) { + if(mat->passes[0].alphatest) { + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, 0.9f); + } else { + glDisable(GL_ALPHA_TEST); + } + } + + if(!k3IsCore) { + glMatrixMode(GL_MODELVIEW); + mat4 modelview; + glm_mat4_mul(view, modelmat, modelview); + glLoadMatrixf((float*) modelview); + } + + glBindBufferARB(GL_ARRAY_BUFFER_ARB, mdl->vstore->gl); + glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, mdl->estore->gl); + + if(glsl) { + enable_glsl_bones(glsl, mdl, bones); + enable_glsl_tangents(glsl, mdl); + } + + if(k3CPUSkinning) { + apply_cpu_skinning(mdl, bones); + } + + if(isnanf(mat->passes[0].aabb)) { + enable_vertex_buffers(mdl, glsl); + + glDrawElements(GL_TRIANGLES, mesh->idxNumber, GL_UNSIGNED_SHORT, (void*) (mesh->idxStart * 2)); + + disable_vertex_buffers(mdl, glsl); + } else { + push_aabb(mat, 0, mdl); + } + + if(glsl) { + disable_glsl_tangents(glsl, mdl); + disable_glsl_bones(mdl, glsl); + } + } +} + +void k3PassForward(mat4 projection, mat4 cam) { + glm_mat4_copy(cam, CamMat); + glm_mat4_copy(projection, ProjMat); + queuesort(); + + mat4 view; + glm_mat4_inv_fast(cam, view); + + setup_ff_projection(projection); + + setup_arbprog_globals(); + + glEnable(GL_DEPTH_TEST); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDepthMask(GL_TRUE); + + glDepthFunc(GL_LEQUAL); + if(GLAD_GL_EXT_framebuffer_sRGB) { + glEnable(GL_FRAMEBUFFER_SRGB_EXT); + } + glEnable(GL_BLEND); + glEnable(GL_MULTISAMPLE); + + glEnable(GL_CULL_FACE); + glFrontFace(GL_CCW); + + if(!k3IsCore) { + glEnable(GL_NORMALIZE); + } + + int l = 0, k = LightCount; + while(1) { + forward_subpass(view, 0, l, k > 4 ? 4 : k, 0, renderQueueSize); + + l += 4; + k -= 4; + + if(k <= 0) { + break; + } + } + + // Horribly inefficient. + for(size_t rble = 0; rble < renderQueueSize; rble++) { + l = 0, k = LightCount; + while(1) { + forward_subpass(view, 1, l, k > 4 ? 4 : k, rble, rble + 1); + + l += 4; + k -= 4; + + if(k <= 0) { + break; + } + } + } +} + +void k3PassDepthOnly(mat4 projection, mat4 cam, int clear, int cull) { + glm_mat4_copy(cam, CamMat); + glm_mat4_copy(projection, ProjMat); + queuesort(); + + mat4 view; + glm_mat4_inv_fast(cam, view); + + setup_ff_projection(projection); + + setup_arbprog_globals(); + + glEnable(GL_DEPTH_TEST); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glDepthMask(GL_TRUE); + glDepthFunc(GL_LESS); + + if(clear) { + glClear(GL_DEPTH_BUFFER_BIT); + } + + if(GLAD_GL_ARB_fragment_program) + glDisable(GL_FRAGMENT_PROGRAM_ARB); + if(GLAD_GL_ARB_vertex_program) + glDisable(GL_VERTEX_PROGRAM_ARB); + + glFrontFace(GL_CCW); + if(cull) { + glEnable(GL_CULL_FACE); + } else { + glDisable(GL_CULL_FACE); + } + + GLuint lastVP = 0; + GLhandleARB lastGLSL = -1; + + for(size_t rble = 0; rble < renderQueueSize; rble++) { + struct k3Mdl *mdl = renderQueue[rble].mdl; + struct k3Mesh *mesh = renderQueue[rble].mesh; + float *modelmat = (float*) renderQueue[rble].modelmat; + struct k3Mat *mat = &mesh->mat; + struct k3AnimationBone *bones = renderQueue[rble].bones; + + if(mat->passes[0].additive || mat->passes[0].transparent) { + continue; + } + + GLhandleARB glsl = renderQueue[rble].glsl; + GLuint arbvp = renderQueue[rble].arbvp; + + if(glsl) { + if(lastGLSL != glsl) { + if(k3IsCore) + glUseProgram(glsl); + else + glUseProgramObjectARB(glsl); + + lastGLSL = glsl; + + setup_glsl_globals(glsl, view); + } + + setup_core_projection(glsl, projection); + + setup_glsl_mat_uniforms(glsl, mat, 0); + + setup_glsl_model_uniforms(glsl, modelmat); + } else if(!k3IsCore) { + if(lastGLSL && GLAD_GL_ARB_shading_language_100) { + if(k3IsCore) + glUseProgram(0); + else + glUseProgramObjectARB(0); + } + lastGLSL = 0; + + if(arbvp != lastVP) { + if(arbvp && !lastVP) { + glEnable(GL_VERTEX_PROGRAM_ARB); + } else if(!arbvp && lastVP) { + glDisable(GL_VERTEX_PROGRAM_ARB); + } + + if(arbvp) { + glBindProgramARB(GL_VERTEX_PROGRAM_ARB, arbvp); + } + + lastVP = arbvp; + } + } + + if(!k3IsCore) { + glMatrixMode(GL_MODELVIEW); + mat4 modelview; + glm_mat4_mul(view, modelmat, modelview); + glLoadMatrixf((float*) modelview); + } + + glBindBufferARB(GL_ARRAY_BUFFER_ARB, mdl->vstore->gl); + glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, mdl->estore->gl); + + if(glsl) { + enable_glsl_bones(glsl, mdl, bones); + } + + enable_vertex_buffers(mdl, glsl); + + if(k3CPUSkinning) { + apply_cpu_skinning(mdl, bones); + } + + if(isnanf(mat->passes[0].aabb)) { + enable_vertex_buffers(mdl, glsl); + + glDrawElements(GL_TRIANGLES, mesh->idxNumber, GL_UNSIGNED_SHORT, (void*) (mesh->idxStart * 2)); + + disable_vertex_buffers(mdl, glsl); + } else { + push_aabb(mat, 0, mdl); + } + + if(glsl) { + disable_glsl_bones(mdl, glsl); + } + } + + glFrontFace(GL_CCW); +} + +static size_t compute_light_views(mat4 **_projs, mat4 **_cams, char **_isCube) { + mat4 *projs = _mm_malloc(sizeof(*projs) * LightCount * 6, 16); + mat4 *cams = _mm_malloc(sizeof(*cams) * LightCount * 6, 16); + char *isCube = _mm_malloc(sizeof(*isCube) * LightCount * 6, 1); + + size_t s = 0; + + for(int i = 0; i < LightCount; i++) { + isCube[s] = 0; + + struct k3Light *l = &Lights[i]; + + /*if(!l->castShadow) { + continue; + }*/ + + if(l->type == k3_DIRECTIONAL) { + glm_ortho(-60, 60, -60, 60, -60, 60, projs[s]); + + mat4 invmainproj; + glm_mat4_inv_fast(ProjMat, invmainproj); + + mat4 frustummat; + glm_mat4_mul(CamMat, invmainproj, frustummat); + + vec4 corners[8]; + glm_frustum_corners(frustummat, corners); + + vec4 viewcenter; + glm_frustum_center(corners, viewcenter); + + mat4 view; + glm_look_anyup(viewcenter, l->dir.direction, view); + + glm_mat4_inv_fast(view, cams[s]); + + s++; + } else if(l->type == k3_SPOT || l->type == k3_HALF_OMNI) { + glm_perspective(l->spot.angle, 1, 0.1, l->radius, projs[s]); + + mat4 view; + glm_look_anyup(l->spot.position, l->spot.direction, view); + + glm_mat4_inv_fast(view, cams[s]); + + s++; + } else if(l->type == k3_OMNI) { + static const vec3 dirs[] = {{0, 0, -1}, {0, 0, 1}, {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}}; + static const vec3 ups[] = {{0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {0, 0, 1}, {0, 0, -1}}; + + for(int d = 0; d < 6; d++) { + glm_perspective(glm_rad(120), 1, 0.1, l->radius, projs[s]); + + mat4 view; + glm_look(l->omni.position, dirs[d], ups[d], view); + + glm_mat4_inv_fast(view, cams[s]); + + isCube[s] = d != 0; + + s++; + } + } + } + + *_projs = projs; + *_cams = cams; + *_isCube = isCube; + + return s; +} + +#ifdef k3_IRREGULAR_SHADOWS +static void pass_irregular(int passnum, mat4 mainproj, mat4 maincam, mat4 lightproj, mat4 lightcam, int lightshadow, int layer) { + glm_mat4_copy(passnum == 1 ? maincam : lightcam, CamMat); + glm_mat4_copy(passnum == 1 ? mainproj : lightproj, ProjMat); + queuesort(); + + mat4 mainview; + glm_mat4_inv_fast(maincam, mainview); + + mat4 lightview; + glm_mat4_inv_fast(lightcam, lightview); + + mat4 lightvp; + glm_mat4_mul(lightproj, lightview, lightvp); + + setup_ff_projection(passnum == 1 ? mainproj : lightproj); + + setup_arbprog_globals(); + + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + + // For debugging + if(layer == 4) { + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + } + + glEnable(GL_DEPTH_TEST); + + if(passnum == 1) { + glDepthMask(GL_FALSE); + glDepthFunc(GL_EQUAL); + glEnable(GL_CULL_FACE); + } else { + glDepthMask(GL_FALSE); + glDepthFunc(GL_LEQUAL); + glDisable(GL_CULL_FACE); + } + + glFrontFace(GL_CCW); + + if(GLAD_GL_ARB_fragment_program) + glDisable(GL_FRAGMENT_PROGRAM_ARB); + if(GLAD_GL_ARB_vertex_program) + glDisable(GL_VERTEX_PROGRAM_ARB); + + GLuint lastVP = 0; + GLhandleARB lastGLSL = -1; + + for(size_t rble = 0; rble < renderQueueSize; rble++) { + struct k3Mdl *mdl = renderQueue[rble].mdl; + struct k3Mesh *mesh = renderQueue[rble].mesh; + float *modelmat = (float*) renderQueue[rble].modelmat; + struct k3Mat *mat = &mesh->mat; + struct k3AnimationBone *bones = renderQueue[rble].bones; + + if(mat->passes[0].additive || mat->passes[0].transparent) { + continue; + } + + GLhandleARB glsl; + + if(passnum == 1) { + glsl = GL_FROM_K3GLSL(mat->passes[0].glsl.hpIrreg1); + } else if(passnum == 2) { + glsl = GL_FROM_K3GLSL(mat->passes[0].glsl.hpIrreg2); + } + + if(glsl) { + if(lastGLSL != glsl) { + if(k3IsCore) + glUseProgram(glsl); + else + glUseProgramObjectARB(glsl); + + lastGLSL = glsl; + + setup_glsl_globals(glsl, passnum == 1 ? mainview : lightview); + } + + setup_core_projection(glsl, passnum == 1 ? mainproj : lightproj); + + setup_glsl_mat_uniforms(glsl, mat, 0); + + glBindBufferBaseEXT(GL_SHADER_STORAGE_BUFFER, 0, LightShadows[lightshadow].multimapEls); + + if(passnum == 2) { + glUniform1iARB(glGetUniformLocationARB(glsl, "u_lightnum"), lightshadow); + } + + if(glBindImageTexture) { + glBindImageTexture(0, LightShadows[lightshadow].multimapHeads, 0, GL_FALSE, layer, GL_READ_WRITE_ARB, GL_R32UI); + glBindImageTexture(1, IrregPixelsInShadow, 0, GL_FALSE, 0, GL_READ_WRITE_ARB, GL_R32UI); + } else { + glBindImageTextureEXT(0, LightShadows[lightshadow].multimapHeads, 0, GL_FALSE, layer, GL_READ_WRITE_ARB, GL_R32UI); + glBindImageTextureEXT(1, IrregPixelsInShadow, 0, GL_FALSE, 0, GL_READ_WRITE_ARB, GL_R32UI); + } + + glUniformMatrix4fvARB(glGetUniformLocationARB(glsl, "u_lightvp"), 1, GL_FALSE, (float*) lightvp); + + setup_glsl_model_uniforms(glsl, modelmat); + } else { + k3Log(k3_WARN, "Missing irregular shadow pass %i shader", passnum); + continue; + } + + if(!k3IsCore) { + glMatrixMode(GL_MODELVIEW); + mat4 modelview; + glm_mat4_mul(passnum == 1 ? mainview : lightview, modelmat, modelview); + glLoadMatrixf((float*) modelview); + } + + glBindBufferARB(GL_ARRAY_BUFFER_ARB, mdl->vstore->gl); + glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, mdl->estore->gl); + + if(glsl) { + enable_glsl_bones(glsl, mdl, bones); + } + + enable_vertex_buffers(mdl, glsl); + + if(k3CPUSkinning) { + apply_cpu_skinning(mdl, bones); + } + + if(isnanf(mat->passes[0].aabb)) { + enable_vertex_buffers(mdl, glsl); + + glDrawElements(GL_TRIANGLES, mesh->idxNumber, GL_UNSIGNED_SHORT, (void*) (mesh->idxStart * 2)); + + disable_vertex_buffers(mdl, glsl); + } else { + push_aabb(mat, 0, mdl); + } + + if(glsl) { + disable_glsl_bones(mdl, glsl); + } + } +} + +#define MULTIMAP_HEADS_SIZE 64 + +void k3PassIrregular(struct k3Offscreen *mainview, mat4 mainproj, mat4 maincam) { + static size_t cachedMainViewResolution = 0; + + size_t mainViewWidth = mainview ? k3TexSzX(mainview->diffuse) : MainWidth; + size_t mainViewHeight = mainview ? k3TexSzY(mainview->diffuse) : MainHeight; + size_t mainViewResolution = mainViewWidth * mainViewHeight; + + if(!IrregPixelsInShadow || cachedMainViewResolution != mainViewResolution) { + if(!IrregPixelsInShadow) { + glGenTextures(1, &IrregPixelsInShadow); + } + + glBindTexture(GL_TEXTURE_2D, IrregPixelsInShadow); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R32UI, mainViewWidth, mainViewHeight, 0, GL_RED_INTEGER_EXT, GL_UNSIGNED_INT, NULL); + } + + mat4 *projs; + mat4 *cams; + char *isCube; + + size_t count = compute_light_views(&projs, &cams, &isCube); + + LightShadowsCount = count; + if(LightShadowsCount > LightShadowsCapacity) { + _mm_free(LightShadows); + + LightShadows = _mm_malloc(sizeof(*LightShadows) * LightShadowsCount, 16); + memset(LightShadows, 0, sizeof(*LightShadows) * LightShadowsCount); + LightShadowsCapacity = LightShadowsCount; + } + + LightShadowIrregularMode = true; + + int ls = -1, layer = 0; + + // TODO: document :) + for(int lv = 0; lv < count; lv++) { + if(isCube[lv]) { + layer++; + } else { + layer = 0; + ls++; + } + + if(!isCube[lv]) { + if(!LightShadows[ls].multimapEls || cachedMainViewResolution != mainViewResolution) { + if(!LightShadows[ls].multimapEls) { + glGenBuffersARB(1, &LightShadows[ls].multimapEls); + } + glBindBufferARB(GL_SHADER_STORAGE_BUFFER, LightShadows[ls].multimapEls); + glBufferDataARB(GL_SHADER_STORAGE_BUFFER, 4 + 24 * (1500000), NULL, GL_STREAM_DRAW_ARB); + } + + if(!LightShadows[ls].multimapHeads) { + glGenTextures(1, &LightShadows[ls].multimapHeads); + glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, LightShadows[ls].multimapHeads); + glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_R32UI, MULTIMAP_HEADS_SIZE, MULTIMAP_HEADS_SIZE, 6, 0, GL_RED_INTEGER_EXT, GL_UNSIGNED_INT, NULL); + } + + // Set multimap heads to all -1, to signify end of linked lists + glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, LightShadows[ls].multimapHeads); + void *allones = malloc(MULTIMAP_HEADS_SIZE * MULTIMAP_HEADS_SIZE * sizeof(GLuint) * 6); + memset(allones, 0xFF, sizeof(GLuint) * MULTIMAP_HEADS_SIZE * MULTIMAP_HEADS_SIZE * 6); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 0, MULTIMAP_HEADS_SIZE, MULTIMAP_HEADS_SIZE, 6, GL_RED_INTEGER_EXT, GL_UNSIGNED_INT, allones); + free(allones); + + // Set next free index to 0 + glBindBufferARB(GL_SHADER_STORAGE_BUFFER, LightShadows[ls].multimapEls); + glBufferSubDataARB(GL_SHADER_STORAGE_BUFFER, 0, sizeof(GLuint), &(GLuint) {0}); + } + + pass_irregular(1, mainproj, maincam, projs[lv], cams[lv], ls, layer); + } + + cachedMainViewResolution = mainViewResolution; + + glMemoryBarrier(GL_ALL_BARRIER_BITS); + + glViewport(0, 0, MULTIMAP_HEADS_SIZE, MULTIMAP_HEADS_SIZE); + + if(GLAD_GL_INTEL_conservative_rasterization) { + glEnable(GL_CONSERVATIVE_RASTERIZATION_INTEL); + } else if(GLAD_GL_NV_conservative_raster) { + glEnable(GL_CONSERVATIVE_RASTERIZATION_NV); + } + + ls = -1, layer = 0; + for(int lv = 0; lv < count; lv++) { + if(isCube[lv]) { + layer++; + } else { + layer = 0; + ls++; + } + + pass_irregular(2, mainproj, maincam, projs[lv], cams[lv], ls, layer); + } + + _mm_free(projs); + _mm_free(cams); + _mm_free(isCube); + + if(GLAD_GL_INTEL_conservative_rasterization) { + glDisable(GL_CONSERVATIVE_RASTERIZATION_INTEL); + } else if(GLAD_GL_NV_conservative_raster) { + glDisable(GL_CONSERVATIVE_RASTERIZATION_NV); + } + + glViewport(0, 0, mainViewWidth, mainViewHeight); + + glMemoryBarrier(GL_ALL_BARRIER_BITS); +} +#else +void k3PassIrregular(struct k3Offscreen *mainview, mat4 mainproj, mat4 maincam) { + k3Log(k3_WARN, "k3_IRREGULAR_SHADOWS is off"); +} +#endif + +// Constructs shadowmap atlas, saves `offscr` for own use +void k3PassShadowmap(mat4 projection, mat4 cam, struct k3Offscreen *offscr) { + glm_mat4_copy(projection, ProjMat); + glm_mat4_copy(cam, CamMat); + + mat4 *projs; + mat4 *cams; + char *isCube; + + size_t count = compute_light_views(&projs, &cams, &isCube); + + ShadowAtlas = offscr; + + LightShadowIrregularMode = false; + + LightShadowsCount = LightCount; + if(LightShadowsCount > LightShadowsCapacity) { + _mm_free(LightShadows); + + LightShadows = _mm_malloc(sizeof(*LightShadows) * LightShadowsCount, 16); + memset(LightShadows, 0, sizeof(*LightShadows) * LightShadowsCount); + LightShadowsCapacity = LightShadowsCount; + } + + if(count == 0) { + return; + } + + if(k3TexSzX(offscr->depth) != k3TexSzY(offscr->depth)) { + k3Log(k3_ERR, "Square texture expected for shadow map atlas"); + return; + } + + int cellsPerDimension = 0; + + if(count == 1) { + cellsPerDimension = 1; + } else { + int cellsTotalLog = 1; + while((1 << cellsTotalLog) < count) { + cellsTotalLog++; + } + cellsPerDimension = 1 << ((cellsTotalLog + 1) / 2); + } + + uint16_t sz = k3TexSzX(offscr->depth); + uint16_t cellSz = sz / cellsPerDimension; + + size_t s = 0; + + k3BeginOffscreen(offscr); + for(size_t i = 0; i < count; i++) { + int cellX = i % cellsPerDimension; + int cellY = i / cellsPerDimension; + + int vp[] = {cellX * cellSz, cellY * cellSz, cellSz, cellSz}; + + mat4 view = GLM_MAT4_IDENTITY_INIT; + glm_mat4_inv(cams[i], view); + + if(!isCube[i]) { + glm_mat4_mul(projs[i], view, LightShadows[s].vp); + glm_vec4_copy((vec4) {i, (float) cellSz / sz, 0, 0}, LightShadows[s].atlasSegment); + s++; + } + + glViewport(vp[0], vp[1], vp[2], vp[3]); + k3PassDepthOnly(projs[i], cams[i], i == 0, false); + } + k3EndOffscreen(offscr); + + _mm_free(projs); + _mm_free(cams); + _mm_free(isCube); +} + +void k3BatchClear() { + renderQueueSize = 0; +} + +int k3CubemapTraditional(struct k3Tex *tex, mat4 proj, mat4 cam) { + if(!tex->cubemap) { + k3Log(k3_ERR, "k3CubemapTraditional accepts only cubemaps"); + return 0; + } + + glDepthMask(GL_FALSE); + + glActiveTexture(GL_TEXTURE0); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + if(!k3IsCore) { + mat4 mv; + glm_mat4_inv_fast(cam, mv); + mv[3][0] = mv[3][1] = mv[3][2] = 0; + + glMatrixMode(GL_PROJECTION); + glLoadMatrixf((float*) proj); + + glMatrixMode(GL_MODELVIEW); + glLoadMatrixf((float*) mv); + + if(!GLAD_GL_ARB_shading_language_100) { + glDisable(GL_LIGHTING); + } + + glDisable(GL_CULL_FACE); + + if(GLAD_GL_ARB_shading_language_100) { + glUseProgramObjectARB(0); + } + if(GLAD_GL_ARB_vertex_program) { + glDisable(GL_VERTEX_PROGRAM_ARB); + } + if(GLAD_GL_ARB_fragment_program) { + glDisable(GL_FRAGMENT_PROGRAM_ARB); + } + + glEnable(GL_TEXTURE_CUBE_MAP); + //glEnable(0x884F); + + glBegin(GL_QUADS); + glColor3f(1, 1, 1); + + for(int z = -1; z <= 1; z += 2) { + glTexCoord3f(-1, -1, z); + glVertex3f(-1, -1, z); + glTexCoord3f(+1, -1, z); + glVertex3f(+1, -1, z); + glTexCoord3f(+1, +1, z); + glVertex3f(+1, +1, z); + glTexCoord3f(-1, +1, z); + glVertex3f(-1, +1, z); + } + for(int x = -1; x <= 1; x += 2) { + glTexCoord3f(x, -1, -1); + glVertex3f(x, -1, -1); + glTexCoord3f(x, +1, -1); + glVertex3f(x, +1, -1); + glTexCoord3f(x, +1, +1); + glVertex3f(x, +1, +1); + glTexCoord3f(x, -1, +1); + glVertex3f(x, -1, +1); + } + for(int y = -1; y <= 1; y += 2) { + glTexCoord3f(-1, y, -1); + glVertex3f(-1, y, -1); + glTexCoord3f(+1, y, -1); + glVertex3f(+1, y, -1); + glTexCoord3f(+1, y, +1); + glVertex3f(+1, y, +1); + glTexCoord3f(-1, y, +1); + glVertex3f(-1, y, +1); + } + glEnd(); + + glDisable(GL_TEXTURE_CUBE_MAP); + } else { + GLuint prog = GL_FROM_K3GLSL(basicCubemapProgram); + + glUseProgram(prog); + + mat4 cam0; + glm_mat4_copy(cam, cam0); + cam0[3][0] = 0; + cam0[3][1] = 0; + cam0[3][2] = 0; + + mat4 pinv; + glm_mat4_inv_fast(proj, pinv); + + mat4 vpinv; + glm_mat4_mul(cam0, pinv, vpinv); + + glUniformMatrix4fv(glGetUniformLocation(prog, "u_vpinv"), 1, GL_FALSE, (float*) vpinv); + + glDrawArrays(GL_TRIANGLES, 0, 3); + + // For some reason this is necessary?!?!?! + // PLEASE SOMEONE FIGURE OUT + glUseProgram(0); + } + + glDepthMask(GL_TRUE); + + return 1; +} + +void GlCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) { + // Some messages end in newlines; remove. + if(message[length - 1] == '\n') { + length--; + } + + // A history is employed to prevent spammings in the debug callback. + + #define HISTORY_SIZE 8 + static char *history[HISTORY_SIZE]; + static int historyNext = 0; + + int newish = 1; + + for(int i = 0; i < HISTORY_SIZE; i++) { + if(history[i] && !strncmp(history[i], message, length)) { + newish = 0; + } + } + + if(newish) { + enum k3LogLevel lvl; + + switch(severity) { + case GL_DEBUG_SEVERITY_HIGH_ARB: + lvl = k3_ERR; + break; + case GL_DEBUG_SEVERITY_MEDIUM_ARB: + lvl = k3_WARN; + break; + default: + lvl = k3_INFO; + break; + } + + k3Log(lvl, "%.*s", length, message); + + history[historyNext] = realloc(history[historyNext], length + 1); + strncpy(history[historyNext], message, length); + history[historyNext][length] = 0; + + historyNext = (historyNext + 1) % HISTORY_SIZE; + } +} + +void k3Init() { + if(GLAD_GL_ARB_debug_output) { + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB); + glDebugMessageCallbackARB(GlCallback, NULL); + } + + GLuint i; + glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &i); + + if(i & GL_CONTEXT_CORE_PROFILE_BIT) { + k3IsCore = 1; + } else { + k3IsCore = 0; + } + + if(k3IsCore) { + GLuint lol; + glGenVertexArrays(1, &lol); + glBindVertexArray(lol); + + basicBlitProgram = k3ProgramGLSL(k3ShaderGLSLV( + "#version 330\n" + "uniform vec2 u_sz;" + "const vec4 positions[3] = vec4[3] (vec4(-1, -1, 0, 1), vec4(3, -1, 0, 1), vec4(-1, 3, 0, 1));\n" + "out vec2 v_uv;\n" + "void main() {\n" + " v_uv = positions[gl_VertexID].xy * (1.0 + 1.0 / u_sz) * 0.5 + 0.5;\n" + " gl_Position = positions[gl_VertexID];\n" + "}\n", + NULL), k3ShaderGLSLF( + "#version 330\n" + "uniform sampler2D u_tex;\n" + "in vec2 v_uv;\n" + "out vec4 fragcol;\n" + "void main() {\n" + " fragcol = texture2D(u_tex, v_uv);\n" + "}\n", + NULL), NULL); + + basicCubemapProgram = k3ProgramGLSL(k3ShaderGLSLV( + "#version 330\n" + "const vec4 positions[3] = vec4[3] (vec4(-1, -1, 0, 1), vec4(3, -1, 0, 1), vec4(-1, 3, 0, 1));\n" + "uniform mat4 u_vpinv;\n" + "out vec3 v_uv;\n" + "void main() {\n" + " v_uv = (u_vpinv * vec4(positions[gl_VertexID].xyz, 1)).xyz;\n" + " gl_Position = positions[gl_VertexID];\n" + "}\n" + , NULL), k3ShaderGLSLF( + "#version 330\n" + "uniform samplerCube u_tex;\n" + "in vec3 v_uv;\n" + "out vec4 fragcolor;\n" + "void main() {\n" + " fragcolor = textureCube(u_tex, v_uv);\n" + "}\n" + , NULL), NULL); + } +} + +static size_t preprocess(char *src, const char*(*ldr)(const char *fn), const char ***strs, GLuint **sizes) { + size_t len = 1; + *strs = calloc(len, sizeof(**strs)); + *sizes = calloc(len, sizeof(**sizes)); + + (*strs)[0] = src; + (*sizes)[0] = strlen(src); + + for(size_t i = 0; i < len; i++) { + char *include = strstr((*strs)[i], "#include "); + + if(include && (include == (*strs)[i] || include[-1] == '\n')) { + + char *end = strchr(include, '\n'); + + *end = 0; + + if(!ldr) { + + k3Log(k3_ERR, "Cannot include %s without a loader.", include + 9); + + } else { + + len += 2; + *strs = realloc(*strs, sizeof(**strs) * (len)); + *sizes = realloc(*sizes, sizeof(**sizes) * (len)); + + memmove(*strs + i + 3, *strs + i + 1, sizeof(**strs) * (len - i - 3)); + memmove(*sizes + i + 3, *sizes + i + 1, sizeof(**sizes) * (len - i - 3)); + + (*sizes)[i] = include - (*strs)[i]; + + (*strs)[i + 1] = ldr(include + 9); + (*sizes)[i + 1] = strlen((*strs)[i + 1]); + + (*strs)[i + 2] = end + 1; + (*sizes)[i + 2] = strlen((*strs)[i + 2]); + + } + + } + } + + return len; +} + +struct k3GLSLV *k3ShaderGLSLV(const char *src_, const char*(*ldr)(const char *fn)) { + GLhandleARB p; + + char *src = strdup(src_); + + const char **strs; + GLuint *sizes; + size_t listsz = preprocess(src, ldr, &strs, &sizes); + + if(!k3IsCore) { + p = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB); + + glShaderSourceARB(p, listsz, strs, sizes); + + glCompileShaderARB(p); + + int i; + glGetObjectParameterivARB(p, GL_OBJECT_COMPILE_STATUS_ARB, &i); + if(i == GL_FALSE) { + char buf[256]; + GLsizei len; + glGetInfoLogARB(p, sizeof(buf), &len, buf); + + k3Log(k3_ERR, "Failed to compile GLSL vertex shader: %.*s", len, buf); + } + } else { + p = glCreateShader(GL_VERTEX_SHADER); + + glShaderSource(p, listsz, strs, sizes); + + glCompileShader(p); + + int i; + glGetShaderiv(p, GL_COMPILE_STATUS, &i); + if(i == GL_FALSE) { + char buf[256]; + GLsizei len; + glGetShaderInfoLog(p, sizeof(buf), &len, buf); + + k3Log(k3_ERR, "Failed to compile GLSL vertex shader: %.*s", len, buf); + } + } + + free(strs); + free(sizes); + free(src); + + return (struct k3GLSLV*) (uintptr_t) p; +} + +struct k3GLSLF *k3ShaderGLSLF(const char *src_, const char*(*ldr)(const char *fn)) { + GLhandleARB p; + + char *src = strdup(src_); + + const char **strs; + GLuint *sizes; + size_t listsz = preprocess(src, ldr, &strs, &sizes); + + if(!k3IsCore) { + p = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); + + glShaderSourceARB(p, listsz, strs, sizes); + + glCompileShaderARB(p); + + int i; + glGetObjectParameterivARB(p, GL_OBJECT_COMPILE_STATUS_ARB, &i); + if(i == GL_FALSE) { + char buf[256]; + GLsizei len; + glGetInfoLogARB(p, sizeof(buf), &len, buf); + + k3Log(k3_ERR, "Failed to compile GLSL fragment shader: %.*s", len, buf); + } + } else { + p = glCreateShader(GL_FRAGMENT_SHADER); + + glShaderSource(p, listsz, strs, sizes); + + glCompileShader(p); + + int i; + glGetShaderiv(p, GL_COMPILE_STATUS, &i); + if(i == GL_FALSE) { + char buf[256]; + GLsizei len; + glGetShaderInfoLog(p, sizeof(buf), &len, buf); + + k3Log(k3_ERR, "Failed to compile GLSL fragment shader: %.*s", len, buf); + } + } + + free(strs); + free(sizes); + free(src); + + return (struct k3GLSLF*) (uintptr_t) p; +} + +struct k3GLSLG *k3ShaderGLSLG(const char *src_, const char*(*ldr)(const char *fn)) { + GLhandleARB p = glCreateShaderObjectARB(GL_GEOMETRY_SHADER_EXT); + + char *src = strdup(src_); + + const char **strs; + GLuint *sizes; + size_t listsz = preprocess(src, ldr, &strs, &sizes); + + glShaderSourceARB(p, listsz, strs, sizes); + + glCompileShaderARB(p); + + int i; + glGetObjectParameterivARB(p, GL_OBJECT_COMPILE_STATUS_ARB, &i); + if(i == GL_FALSE) { + char buf[256]; + GLsizei len; + glGetInfoLogARB(p, sizeof(buf), &len, buf); + + k3Log(k3_ERR, "Failed to compile GLSL geometry shader: %.*s", len, buf); + } + + free(strs); + free(sizes); + free(src); + + return (struct k3GLSLG*) (uintptr_t) p; +} + +struct k3GLSLP *k3ProgramGLSL(struct k3GLSLV *vs, struct k3GLSLF *fs, struct k3GLSLG *gs) { + GLhandleARB prog; + + if(!k3IsCore) { + prog = glCreateProgramObjectARB(); + + glAttachObjectARB(prog, (uintptr_t) vs); + glAttachObjectARB(prog, (uintptr_t) fs); + + if(gs) glAttachObjectARB(prog, (uintptr_t) gs); + + glLinkProgramARB(prog); + + int i; + glGetObjectParameterivARB(prog, GL_OBJECT_LINK_STATUS_ARB, &i); + if(i == GL_FALSE) { + char buf[256]; + GLsizei len; + glGetInfoLogARB(prog, sizeof(buf), &len, buf); + + k3Log(k3_ERR, "Failed to link GLSL program: %.*s", len, buf); + } + + GLint maxLength; + glGetObjectParameterivARB(prog, GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB, &maxLength); + + char *name = alloca(maxLength + 1); + memset(name, 0, maxLength + 1); + + GLint uniformCount; + glGetObjectParameterivARB(prog, GL_OBJECT_ACTIVE_UNIFORMS_ARB, &uniformCount); + + for(i = 0; i < uniformCount; i++) { + int size; + int type; + glGetActiveUniformARB(prog, i, maxLength, NULL, &size, &type, name); + + k3Log(k3_DEBUG, "%i %s", size, name); + } + } else { + prog = glCreateProgram(); + + glAttachShader(prog, (uintptr_t) vs); + glAttachShader(prog, (uintptr_t) fs); + + if(gs) glAttachShader(prog, (uintptr_t) gs); + + glLinkProgram(prog); + + int i; + glGetProgramiv(prog, GL_LINK_STATUS, &i); + if(i == GL_FALSE) { + char buf[256]; + GLsizei len; + glGetProgramInfoLog(prog, sizeof(buf), &len, buf); + + k3Log(k3_ERR, "Failed to link GLSL program: %.*s", len, buf); + } + + GLint maxLength; + glGetProgramiv(prog, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxLength); + + char *name = alloca(maxLength + 1); + memset(name, 0, maxLength + 1); + + GLint uniformCount; + glGetProgramiv(prog, GL_ACTIVE_UNIFORMS, &uniformCount); + + for(i = 0; i < uniformCount; i++) { + int size; + int type; + glGetActiveUniform(prog, i, maxLength, NULL, &size, &type, name); + + k3Log(k3_DEBUG, "%i %s", size, name); + } + } + + return (struct k3GLSLP*) (uintptr_t) prog; +} + +uint16_t k3ProgramGetUId(struct k3GLSLP *p, const char *key) { + return glGetUniformLocationARB(GL_FROM_K3GLSL(p), key); +} + +struct k3ARBVP *k3ProgramARBVP(const char *src) { + if(!GLAD_GL_ARB_vertex_program) { + return NULL; + } + + GLuint p; + glGenProgramsARB(1, &p); + glBindProgramARB(GL_VERTEX_PROGRAM_ARB, p); + glGetError(); + glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(src), src); + if(glGetError() == GL_INVALID_OPERATION) { + puts(glGetString(GL_PROGRAM_ERROR_STRING_ARB)); + return NULL; + } + + /*printf("VP\n"); + + GLint i; + glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_INSTRUCTIONS_ARB, &i); + printf("\tTotal instructions: %i\n", i); + glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_ALU_INSTRUCTIONS_ARB, &i); + printf("\tTotal ALU instructions: %i\n", i); + glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_TEX_INSTRUCTIONS_ARB, &i); + printf("\tTotal TEX instructions: %i\n", i); + glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_TEX_INDIRECTIONS_ARB, &i); + printf("\tTotal TEX indirections: %i\n", i); + glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_TEMPORARIES_ARB, &i); + printf("\tTotal temporaries: %i\n", i); + glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_PARAMETERS_ARB, &i); + printf("\tTotal parameters: %i\n", i); + glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_ATTRIBS_ARB, &i); + printf("\tTotal attribs: %i\n", i); + glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_ADDRESS_REGISTERS_ARB, &i); + printf("\tTotal addresses: %i\n", i);*/ + + return (struct k3ARBVP*) (uintptr_t) p; +} + +struct k3ARBFP *k3ProgramARBFP(const char *src) { + if(!GLAD_GL_ARB_fragment_program) { + return NULL; + } + + GLuint p; + glGenProgramsARB(1, &p); + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, p); + glGetError(); + glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(src), src); + if(glGetError() == GL_INVALID_OPERATION) { + puts(glGetString(GL_PROGRAM_ERROR_STRING_ARB)); + return NULL; + } + + /*printf("FP\n"); + + GLint i; + glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_INSTRUCTIONS_ARB, &i); + printf("\tTotal instructions: %i\n", i); + glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_ALU_INSTRUCTIONS_ARB, &i); + printf("\tTotal ALU instructions: %i\n", i); + glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_TEX_INSTRUCTIONS_ARB, &i); + printf("\tTotal TEX instructions: %i\n", i); + glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_TEX_INDIRECTIONS_ARB, &i); + printf("\tTotal TEX indirections: %i\n", i); + glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_TEMPORARIES_ARB, &i); + printf("\tTotal temporaries: %i\n", i); + glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_PARAMETERS_ARB, &i); + printf("\tTotal parameters: %i\n", i); + glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_ATTRIBS_ARB, &i); + printf("\tTotal attribs: %i\n", i);*/ + + return (struct k3ARBFP*) (uintptr_t) p; +} + +struct k3Offscreen *k3OffscreenCreate(struct k3Tex *diffuse, struct k3Tex *depth) { + k3Log(k3_INFO, "Init %sFBO", !diffuse && depth ? "depth-only " : ""); + + GLuint fbo = 0; + if(GLAD_GL_EXT_framebuffer_object) { + glGenFramebuffersEXT(1, &fbo); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); + + if(diffuse) { + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, GL_FROM_K3TEX(diffuse), 0); + } else { + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + } + + if(depth) { + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, GL_FROM_K3TEX(depth), 0); + } + + if(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) { + k3Log(k3_WARN, "Framebuffer incomplete"); + } + + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + glDrawBuffer(GL_BACK); // XXX: this should not be necessary + } else { + k3Log(k3_ERR, "Non-FBO offscreens not implemented"); + return NULL; + } + + struct k3Offscreen *ret = malloc(sizeof(*ret)); + ret->fbo = fbo; + ret->diffuse = diffuse; + ret->depth = depth; + return ret; +} + +void k3BeginOffscreen(struct k3Offscreen *offscr) { + if(GLAD_GL_EXT_framebuffer_object) { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, offscr->fbo); + + struct k3Tex *t = offscr->diffuse ? offscr->diffuse : offscr->depth; + glViewport(0, 0, k3TexSzX(t), k3TexSzY(t)); + } else { + k3Log(k3_ERR, "Non-FBO offscreens not implemented"); + } +} + +void k3EndOffscreen(struct k3Offscreen *offscr) { + if(GLAD_GL_EXT_framebuffer_object) { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + glViewport(0, 0, MainWidth, MainHeight); + } else { + k3Log(k3_ERR, "Non-FBO offscreens not implemented"); + } +} + +void k3BlitToScreen(struct k3Offscreen *offscr, int additive) { + k3BlitToScreenEffect(offscr, additive, k3_NONE, NULL, NULL); +} + +void k3BlitToScreenEffect(struct k3Offscreen *offscr, int additive, int effect, void *program, void *params) { + if(GLAD_GL_EXT_framebuffer_object) { + glActiveTexture(GL_TEXTURE0); + + glBindTexture(GL_TEXTURE_2D, GL_FROM_K3TEX(offscr->diffuse)); + + if(additive) { + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE); + } else { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDisable(GL_BLEND); + } + + if(!k3IsCore) { + glDisable(GL_LIGHTING); + glDisable(GL_NORMALIZE); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glEnable(GL_TEXTURE_2D); + } + + glDisable(GL_DEPTH_TEST); + + if(GLAD_GL_ARB_vertex_program) { + glDisable(GL_VERTEX_PROGRAM_ARB); + } + + if(GLAD_GL_ARB_fragment_program) { + if(effect != k3_ARBFRAG) { + glDisable(GL_FRAGMENT_PROGRAM_ARB); + } else { + glEnable(GL_FRAGMENT_PROGRAM_ARB); + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, effect == k3_ARBFRAG ? GL_FROM_K3ARBFP(program) : 0); + } + } + + if(k3IsCore) { + if(effect == k3_NONE) { + glUseProgram(GL_FROM_K3GLSL(basicBlitProgram)); + } else { + assert(effect == k3_GLSL); + + glUseProgram(GL_FROM_K3GLSL(program)); + } + + GLint uSz = glGetUniformLocation(GL_FROM_K3GLSL(basicBlitProgram), "u_sz"); + glUniform2f(uSz, k3TexSzX(offscr->diffuse), k3TexSzY(offscr->diffuse)); + + glDrawArrays(GL_TRIANGLES, 0, 3); + } else { + if(GLAD_GL_ARB_shader_objects) { + glUseProgramObjectARB(effect == k3_GLSL ? GL_FROM_K3GLSL(program) : 0); + } + + float bleedW = 1.0f / k3TexSzX(offscr->diffuse); + float bleedH = 1.0f / k3TexSzY(offscr->diffuse); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBegin(GL_QUADS); + glColor4f(1, 1, 1, 1); + glTexCoord2f(bleedW, bleedH); + glVertex2f(-1, -1); + glTexCoord2f(1 - bleedW, bleedH); + glVertex2f(1, -1); + glTexCoord2f(1 - bleedW, 1 - bleedH); + glVertex2f(1, 1); + glTexCoord2f(bleedW, 1 - bleedH); + glVertex2f(-1, 1); + glEnd(); + } + } else { + k3Log(k3_ERR, "Non-FBO offscreens not implemented"); + } +} + +void k3OffscreenDestroy(struct k3Offscreen *offscr) { + +} + +static k3LogCallback CallbackFunc; +void k3SetLogCallback(k3LogCallback fun) { + CallbackFunc = fun; +} +void k3Log(enum k3LogLevel level, const char *format, ...) { + static char *buf = NULL; + static size_t bufLen = 0; + + va_list vl; + va_start(vl, format); + + va_list vlc; + va_copy(vlc, vl); + size_t len = vsnprintf(NULL, 0, format, vlc) + 1; + if(len > bufLen) { + buf = realloc(buf, len); + } + va_end(vlc); + + vsnprintf(buf, len, format, vl); + + va_end(vl); + + CallbackFunc(level, buf, len); +} + +uint16_t k3TexSzMax() { + GLint i; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &i); + + return i; +} diff --git a/src/k3.h b/src/k3.h new file mode 100644 index 0000000..7dc9f01 --- /dev/null +++ b/src/k3.h @@ -0,0 +1,212 @@ +#pragma once + +#include +#include +#include +#include +#include + +void k3Init(); +void k3Resize(uint16_t width, uint16_t height); + +enum k3TexType { + k3_DIFFUSE, k3_NORMAL, k3_DISPLACEMENT, k3_EMISSION, k3_ROUGHNESS, k3_ALPHA, + k3_DEPTH, + k3_CUBEMAP, + k3_RAWCOLOR, +}; + +struct k3Tex; +struct k3Tex *k3TexCreate(enum k3TexType type); +void k3TexUpdate(struct k3Tex*, enum k3TexType type, int index, uint16_t width, uint16_t height, void *data); +uint32_t k3TexSzX(struct k3Tex*); +uint32_t k3TexSzY(struct k3Tex*); +uint32_t k3TexSzZ(struct k3Tex*); + +struct k3GLSLV; +struct k3GLSLV *k3ShaderGLSLV(const char *src_, const char*(*ldr)(const char *fn)); + +struct k3GLSLF; +struct k3GLSLF *k3ShaderGLSLF(const char *src_, const char*(*ldr)(const char *fn)); + +struct k3GLSLG; +struct k3GLSLG *k3ShaderGLSLG(const char *src_, const char*(*ldr)(const char *fn)); + +struct k3GLSLP; +struct k3GLSLP *k3ProgramGLSL(struct k3GLSLV*, struct k3GLSLF*, struct k3GLSLG*); +uint16_t k3ProgramGetUId(struct k3GLSLP*, const char *key); + +struct k3ARBVP; +struct k3ARBVP *k3ProgramARBVP(const char *src); + +struct k3ARBFP; +struct k3ARBFP *k3ProgramARBFP(const char *src); + +extern uint8_t k3GraphicalReduction; +extern int k3IsCore; + +struct k3Mat { + struct { + vec4 diffuse; + vec4 specular; + vec4 emission; + float shininess; + } primitive; + struct { + struct { + struct k3ARBVP *vp; + } arbvp; + struct { + struct k3ARBFP *fp; + } arbfp; + struct { + struct k3GLSLP *hp; + +#ifdef k3_IRREGULAR_SHADOWS + struct k3GLSLP *hpIrreg1; + struct k3GLSLP *hpIrreg2; +#endif + + uint8_t uCount; + struct { + char name[16]; + int8_t id; +#define k3_MAT_UNIFORM_I1 0 +#define k3_MAT_UNIFORM_F1 4 + uint8_t type; + union { + int i1; + float f1; + }; +#define k3_MAX_GLSL_UNIFORMS 8 + } u[k3_MAX_GLSL_UNIFORMS]; + } glsl; + +#define k3_MAX_GLSL_UNITS 8 + struct k3Tex *units[k3_MAX_GLSL_UNITS]; + int unitsUsed; + + float aabb; + char additive; + char transparent; + char nocull; + char alphatest; + } passes[1]; +}; + +struct k3AnimationBone { + vec4 translation; + versor rotation; +}; +struct k3Animation { + struct k3AnimationBone *frames; + mat4 *invBind; + uint8_t *boneParents; + uint16_t fps; + uint16_t frameCount; + uint16_t boneCount; + uint16_t id; +}; + +struct k3Animator { + float time; + int loop; + + mat4 *inter; + + struct k3Animation *base; +}; + +struct k3Offscreen; + +enum k3LightType { + k3_DIRECTIONAL, + k3_SPOT, + k3_OMNI, + k3_HALF_OMNI, + k3_AMBIENT, +}; +struct k3Light { + enum k3LightType type; + _Alignas(16) int castShadow; + _Alignas(16) vec4 color; + _Alignas(16) float radius; + _Alignas(16) union { + struct { + vec4 direction; + } dir; + struct { + vec4 position; + vec4 direction; + float angle; + } spot; + struct { + vec4 position; + } omni; + }; +}; + +struct k3Mesh { + uint16_t idxStart, idxNumber; + struct k3Mat mat; +}; + +struct k3Storage; +void k3StorageRef(struct k3Storage*); +void k3StorageUnref(struct k3Storage*); + +struct k3Mdl; +struct k3Mdl *k3MdlCreate(size_t verts, size_t indices, size_t boneCount, vec3 *pos, uint8_t *nrm, float *uvs, uint8_t *cols, uint8_t *boneids, uint16_t *boneweights, uint16_t *inds, mat4 *invBind, uint8_t *boneParents); +void k3MdlUpdatePos(struct k3Mdl *mdl, vec3 *pos); +void k3MdlAddMesh(struct k3Mdl*, struct k3Mat*, uint16_t idxStart, uint16_t idxNumber); +struct k3Mesh *k3MdlGetMeshes(struct k3Mdl*, size_t *count); +void k3MdlAddAnim(struct k3Mdl*, struct k3Animation*); +struct k3Animation *k3MdlGetAnim(struct k3Mdl*, uint16_t id); +size_t k3MdlGetBoneCount(struct k3Mdl*); + +void k3MdlSetDebugName(struct k3Mdl*, const char*); + +struct k3Mdl *k3MdlCopySubs(struct k3Mdl*); + +void k3AnimatorSet(struct k3Animator*, float); +void k3AnimatorStep(struct k3Animator*, float); + +void k3SetLights(size_t, struct k3Light*); +struct k3Light *k3GetLights(size_t*); + +void k3Clear(); + +void k3Batch(struct k3Mdl*, mat4, struct k3AnimationBone* bones); +void k3BatchClear(); + +void k3PassForward(mat4 projection, mat4 cam); +void k3PassDepthOnly(mat4 projection, mat4 cam, int clear, int cull); + +void k3PassShadowmap(mat4 projection, mat4 cam, struct k3Offscreen *offscr); + +struct k3Offscreen; +struct k3Offscreen *k3OffscreenCreate(struct k3Tex *diffuse, struct k3Tex *depth); +void k3BeginOffscreen(struct k3Offscreen*); +void k3EndOffscreen(struct k3Offscreen*); +void k3OffscreenDestroy(struct k3Offscreen*); + +#define k3_NONE 0 +#define k3_GLSL 1 +#define k3_ARBFRAG 2 +void k3BlitToScreenEffect(struct k3Offscreen *offscr, int additive, int effect, void *program, void *params); +void k3BlitToScreen(struct k3Offscreen *offscr, int additive); + +int k3CubemapTraditional(struct k3Tex*, mat4 proj, mat4 cam); + +void k3SetTime(float t); + +enum k3LogLevel { + k3_DEBUG, k3_INFO, k3_WARN, k3_ERR +}; +typedef void(*k3LogCallback)(enum k3LogLevel, const char *str, size_t len); +void k3SetLogCallback(k3LogCallback); +void k3Log(enum k3LogLevel, const char *format, ...); + +uint16_t k3TexSzMax(); + +void k3PassIrregular(struct k3Offscreen *main, mat4 mainproj, mat4 maincam); diff --git a/src/k3batch.c b/src/k3batch.c new file mode 100644 index 0000000..c2fa228 --- /dev/null +++ b/src/k3batch.c @@ -0,0 +1,224 @@ +#include"k3batch.h" + +#include"gl.h" +#include + +struct S { + struct k3RectF src; + struct k3RectF dst; + vec4 color; + float rot; + char additive; +}; + +static struct k3Tex *activeTex; +static size_t SCount, SCapacity; +static struct S *S; + +static struct k3GLSLP *coreProg; +static GLuint coreVBO; + +void k3BatchInit() { + if(k3IsCore) { + coreProg = k3ProgramGLSL(k3ShaderGLSLV( + "#version 330\n" + "in vec2 a_pos;\n" + "in vec2 a_uv;\n" + "in vec4 a_color;\n" + "out vec2 v_uv;\n" + "out vec4 v_color;\n" + "void main() {\n" + " v_uv = a_uv;\n" + " v_color = a_color;\n" + " gl_Position = vec4(vec2(a_pos.x / 3600.0, a_pos.y / 2025.0) * 2.0 - 1.0, 0.0, 1.0);\n" + "}\n" + , NULL), k3ShaderGLSLF( + "#version 330\n" + "uniform sampler2D u_tex;\n" + "uniform float u_texuse;\n" + "in vec2 v_uv;\n" + "in vec4 v_color;\n" + "out vec4 fragcolor;\n" + "void main() {\n" + " fragcolor = mix(vec4(1, 1, 1, 1), texture2D(u_tex, v_uv), u_texuse) * v_color;\n" + "}\n" + , NULL), NULL); + + glGenBuffers(1, &coreVBO); + } +} + +void k3BatchAdd(struct k3Tex *tex, struct k3RectF src, struct k3RectF dst, float rot, vec4 color) { + if(activeTex != tex) { + k3BatchFlush(); + activeTex = tex; + } + + if(SCount == SCapacity) { + struct S *new = _mm_malloc(sizeof(*S) * (SCapacity + 32), 16); + if(S) { + memcpy(new, S, sizeof(*S) * SCapacity); + _mm_free(S); + } + S = new; + SCapacity += 32; + } + + S[SCount++] = (struct S) { + .src = src, + .dst = dst, + .rot = rot, + .color = {color[0], color[1], color[2], color[3]}, + }; +} + +void k3BatchFlush() { + if(!k3IsCore) { + glDisable(GL_NORMALIZE); + + // Some drivers crash on glEnable(GL_LIGHTING) *AND* glDisable(GL_LIGHTING) for some reason + // Just don't do it unless fixed-function is explicitly on + glDisable(GL_LIGHTING); + if(!GLAD_GL_ARB_shading_language_100) { + } + } + + glDisable(GL_DEPTH_TEST); + + if(GLAD_GL_ARB_vertex_program) { + glDisable(GL_VERTEX_PROGRAM_ARB); + } + if(GLAD_GL_ARB_fragment_program) { + glDisable(GL_FRAGMENT_PROGRAM_ARB); + } + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glActiveTexture(GL_TEXTURE0); + + if(activeTex) { + if(!k3IsCore) glEnable(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, *(GLuint*) activeTex); + } else { + if(!k3IsCore) glDisable(GL_TEXTURE_2D); + } + + if(k3IsCore) { + glUseProgram((GLuint) coreProg); + + float *farr = alloca(SCount * 48 * sizeof(*farr)); + + struct S *s = S; + for(size_t i = 0; i < SCount; i++) { + farr[i * 48 + 0] = s->dst.x; + farr[i * 48 + 1] = s->dst.y; + farr[i * 48 + 2] = s->src.x; + farr[i * 48 + 3] = s->src.y + s->src.h; + farr[i * 48 + 4] = s->color[0]; + farr[i * 48 + 5] = s->color[1]; + farr[i * 48 + 6] = s->color[2]; + farr[i * 48 + 7] = s->color[3]; + + farr[i * 48 + 8] = s->dst.x + s->dst.w; + farr[i * 48 + 9] = s->dst.y; + farr[i * 48 + 10] = s->src.x + s->src.w; + farr[i * 48 + 11] = s->src.y + s->src.h; + farr[i * 48 + 12] = s->color[0]; + farr[i * 48 + 13] = s->color[1]; + farr[i * 48 + 14] = s->color[2]; + farr[i * 48 + 15] = s->color[3]; + + farr[i * 48 + 16] = s->dst.x + s->dst.w; + farr[i * 48 + 17] = s->dst.y + s->dst.h; + farr[i * 48 + 18] = s->src.x + s->src.w; + farr[i * 48 + 19] = s->src.y; + farr[i * 48 + 20] = s->color[0]; + farr[i * 48 + 21] = s->color[1]; + farr[i * 48 + 22] = s->color[2]; + farr[i * 48 + 23] = s->color[3]; + + farr[i * 48 + 24] = s->dst.x; + farr[i * 48 + 25] = s->dst.y; + farr[i * 48 + 26] = s->src.x; + farr[i * 48 + 27] = s->src.y + s->src.h; + farr[i * 48 + 28] = s->color[0]; + farr[i * 48 + 29] = s->color[1]; + farr[i * 48 + 30] = s->color[2]; + farr[i * 48 + 31] = s->color[3]; + + farr[i * 48 + 32] = s->dst.x + s->dst.w; + farr[i * 48 + 33] = s->dst.y + s->dst.h; + farr[i * 48 + 34] = s->src.x + s->src.w; + farr[i * 48 + 35] = s->src.y; + farr[i * 48 + 36] = s->color[0]; + farr[i * 48 + 37] = s->color[1]; + farr[i * 48 + 38] = s->color[2]; + farr[i * 48 + 39] = s->color[3]; + + farr[i * 48 + 40] = s->dst.x; + farr[i * 48 + 41] = s->dst.y + s->dst.h; + farr[i * 48 + 42] = s->src.x; + farr[i * 48 + 43] = s->src.y; + farr[i * 48 + 44] = s->color[0]; + farr[i * 48 + 45] = s->color[1]; + farr[i * 48 + 46] = s->color[2]; + farr[i * 48 + 47] = s->color[3]; + + s++; + } + + glBindBufferARB(GL_ARRAY_BUFFER_ARB, coreVBO); + glBufferDataARB(GL_ARRAY_BUFFER_ARB, SCount * 48 * sizeof(*farr), farr, GL_DYNAMIC_DRAW); + + glUniform1f(glGetUniformLocation((GLuint) coreProg, "u_texuse"), !!activeTex); + + GLint aPos = glGetAttribLocation((GLuint) coreProg, "a_pos"); + GLint aUv = glGetAttribLocation((GLuint) coreProg, "a_uv"); + GLint aColor = glGetAttribLocation((GLuint) coreProg, "a_color"); + + glEnableVertexAttribArray(aPos); + glEnableVertexAttribArray(aUv); + glEnableVertexAttribArray(aColor); + + glVertexAttribPointer(aPos, 2, GL_FLOAT, GL_FALSE, 32, (void*) 0); + glVertexAttribPointer(aUv, 2, GL_FLOAT, GL_FALSE, 32, (void*) 8); + glVertexAttribPointer(aColor, 4, GL_FLOAT, GL_FALSE, 32, (void*) 16); + + glDrawArrays(GL_TRIANGLES, 0, SCount * 6); + + glDisableVertexAttribArray(aPos); + glDisableVertexAttribArray(aUv); + glDisableVertexAttribArray(aColor); + } else { + if(GLAD_GL_ARB_shading_language_100) { + glUseProgramObjectARB(0); + } + + glBegin(GL_QUADS); + struct S *s = S; + for(size_t i = 0; i < SCount; i++) { + glColor4f(s->color[0], s->color[1], s->color[2], s->color[3]); + + glTexCoord2f(s->src.x, s->src.y + s->src.h); + glVertex2f(s->dst.x, s->dst.y); + + glTexCoord2f(s->src.x + s->src.w, s->src.y + s->src.h); + glVertex2f(s->dst.x + s->dst.w, s->dst.y); + + glTexCoord2f(s->src.x + s->src.w, s->src.y); + glVertex2f(s->dst.x + s->dst.w, s->dst.y + s->dst.h); + + glTexCoord2f(s->src.x, s->src.y); + glVertex2f(s->dst.x, s->dst.y + s->dst.h); + + s++; + } + glEnd(); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + } + + SCount = 0; +} diff --git a/src/k3batch.h b/src/k3batch.h new file mode 100644 index 0000000..f668772 --- /dev/null +++ b/src/k3batch.h @@ -0,0 +1,14 @@ +#pragma once + +#include"k3.h" +#include + +struct k3RectF { + float x; + float y; + float w; + float h; +}; + +void k3BatchAdd(struct k3Tex *tex, struct k3RectF src, struct k3RectF dst, float rot, vec4 color); +void k3BatchFlush(); \ No newline at end of file diff --git a/src/k3bloom.c b/src/k3bloom.c new file mode 100644 index 0000000..960fdb4 --- /dev/null +++ b/src/k3bloom.c @@ -0,0 +1,286 @@ +#include"k3bloom.h" + +#include +#include"k3.h" +#include +#include"gl.h" + +static struct k3GLSLP *tonemapper; + +static struct k3GLSLP *minibloom1; +static struct k3GLSLP *minibloom2; + +int make_mb1() { + struct k3GLSLV *v = !k3IsCore ? k3ShaderGLSLV( + "uniform vec2 u_sz;" "\n" + "varying vec2 v_uv;" "\n" + "void main() {" "\n" + " v_uv = gl_Vertex.xy * (1.0 + 1.0 / u_sz) * 0.5 + 0.5;""\n" + " gl_Position = vec4(gl_Vertex.xy, 0, 1);" "\n" + "}" "\n" + , NULL) : k3ShaderGLSLV( + "#version 330\n" + "uniform vec2 u_sz;" "\n" + "const vec4 positions[3] = vec4[3] (vec4(-1, -1, 0, 1), vec4(3, -1, 0, 1), vec4(-1, 3, 0, 1));\n" + "out vec2 v_uv;" "\n" + "void main() {" "\n" + " v_uv = positions[gl_VertexID].xy * (1.0 + 1.0 / u_sz) * 0.5 + 0.5;""\n" + " gl_Position = positions[gl_VertexID];" "\n" + "}" "\n" + , NULL); + + if(!v) return 0; + + struct k3GLSLF *f = !k3IsCore ? k3ShaderGLSLF( + "#define THRESHOLD 0.7" "\n" + "#define FACTOR 0.08" "\n" + "uniform sampler2D u_tex;" "\n" + "varying vec2 v_uv;" "\n" + "vec3 get_col(vec2 uv) {" "\n" + " vec3 col = texture2D(u_tex, uv).rgb;" "\n" + " float luminance = col.r * 0.3086 + col.g * 0.6094 + col.b * 0.0820;" "\n" + " if(luminance < THRESHOLD) col = vec3(0);" "\n" + " return col * FACTOR;" "\n" + "}" "\n" + "void main() {" "\n" + " vec3 c = vec3(0);" "\n" + " c += 0.0020 * get_col(v_uv + vec2(-4.0 / 512.0, 0));" "\n" + " c += 0.0060 * get_col(v_uv + vec2(-3.0 / 512.0, 0));" "\n" + " c += 0.0606 * get_col(v_uv + vec2(-2.0 / 512.0, 0));" "\n" + " c += 0.2417 * get_col(v_uv + vec2(-1.0 / 512.0, 0));" "\n" + " c += 0.3829 * get_col(v_uv + vec2(+0.0 / 512.0, 0));" "\n" + " c += 0.2417 * get_col(v_uv + vec2(+1.0 / 512.0, 0));" "\n" + " c += 0.0606 * get_col(v_uv + vec2(+2.0 / 512.0, 0));" "\n" + " c += 0.0060 * get_col(v_uv + vec2(+3.0 / 512.0, 0));" "\n" + " c += 0.0020 * get_col(v_uv + vec2(+4.0 / 512.0, 0));" "\n" + " gl_FragColor = vec4(c, 1);" "\n" + "}" "\n" + , NULL) : k3ShaderGLSLF( + "#version 330\n" + "#define THRESHOLD 0.7" "\n" + "#define FACTOR 0.08" "\n" + "uniform sampler2D u_tex;" "\n" + "in vec2 v_uv;" "\n" + "out vec4 fragcolor;" "\n" + "vec3 get_col(vec2 uv) {" "\n" + " vec3 col = texture2D(u_tex, uv).rgb;" "\n" + " float luminance = col.r * 0.3086 + col.g * 0.6094 + col.b * 0.0820;" "\n" + " if(luminance < THRESHOLD) col = vec3(0);" "\n" + " return col * FACTOR;" "\n" + "}" "\n" + "void main() {" "\n" + " vec3 c = vec3(0);" "\n" + " c += 0.0020 * get_col(v_uv + vec2(-4.0 / 512.0, 0));" "\n" + " c += 0.0060 * get_col(v_uv + vec2(-3.0 / 512.0, 0));" "\n" + " c += 0.0606 * get_col(v_uv + vec2(-2.0 / 512.0, 0));" "\n" + " c += 0.2417 * get_col(v_uv + vec2(-1.0 / 512.0, 0));" "\n" + " c += 0.3829 * get_col(v_uv + vec2(+0.0 / 512.0, 0));" "\n" + " c += 0.2417 * get_col(v_uv + vec2(+1.0 / 512.0, 0));" "\n" + " c += 0.0606 * get_col(v_uv + vec2(+2.0 / 512.0, 0));" "\n" + " c += 0.0060 * get_col(v_uv + vec2(+3.0 / 512.0, 0));" "\n" + " c += 0.0020 * get_col(v_uv + vec2(+4.0 / 512.0, 0));" "\n" + " fragcolor = vec4(c, 1);" "\n" + "}" "\n" + , NULL); + + if(!f) return 0; + + minibloom1 = k3ProgramGLSL(v, f, NULL); + + if(!minibloom1) return 0; + + return 1; +} + +int make_mb2() { + struct k3GLSLV *v = !k3IsCore ? k3ShaderGLSLV( + "uniform vec2 u_sz;" "\n" + "varying vec2 v_uv;" "\n" + "void main() {" "\n" + " v_uv = gl_Vertex.xy * (1.0 + 1.0 / u_sz) * 0.5 + 0.5;" "\n" + " gl_Position = vec4(gl_Vertex.xy, 0, 1);" "\n" + "}" "\n" + , NULL) : k3ShaderGLSLV( + "#version 330\n" + "uniform vec2 u_sz;" "\n" + "const vec4 positions[3] = vec4[3] (vec4(-1, -1, 0, 1), vec4(3, -1, 0, 1), vec4(-1, 3, 0, 1));\n" + "out vec2 v_uv;" "\n" + "void main() {" "\n" + " v_uv = positions[gl_VertexID].xy * (1.0 + 1.0 / u_sz) * 0.5 + 0.5;" "\n" + " gl_Position = positions[gl_VertexID];" "\n" + "}" "\n" + , NULL); + + if(!v) return 0; + + struct k3GLSLF *f = !k3IsCore ? k3ShaderGLSLF( + "uniform sampler2D u_tex;" "\n" + "varying vec2 v_uv;" "\n" + "vec3 get_col(vec2 uv) {" "\n" + " vec3 col = texture2D(u_tex, uv).rgb;" "\n" + " return col;" "\n" + "}" "\n" + "void main() {" "\n" + " vec3 c = vec3(0);" "\n" + " c += 0.0020 * get_col(v_uv + vec2(0, -4.0 / 512.0));" "\n" + " c += 0.0060 * get_col(v_uv + vec2(0, -3.0 / 512.0));" "\n" + " c += 0.0606 * get_col(v_uv + vec2(0, -2.0 / 512.0));" "\n" + " c += 0.2417 * get_col(v_uv + vec2(0, -1.0 / 512.0));" "\n" + " c += 0.3829 * get_col(v_uv + vec2(0, +0.0 / 512.0));" "\n" + " c += 0.2417 * get_col(v_uv + vec2(0, +1.0 / 512.0));" "\n" + " c += 0.0606 * get_col(v_uv + vec2(0, +2.0 / 512.0));" "\n" + " c += 0.0060 * get_col(v_uv + vec2(0, +3.0 / 512.0));" "\n" + " c += 0.0020 * get_col(v_uv + vec2(0, +4.0 / 512.0));" "\n" + " gl_FragColor = vec4(c, 1);" "\n" + "}" "\n" + , NULL) : k3ShaderGLSLF( + "#version 330\n" + "uniform sampler2D u_tex;" "\n" + "in vec2 v_uv;" "\n" + "out vec4 fragcolor;" "\n" + "vec3 get_col(vec2 uv) {" "\n" + " vec3 col = texture2D(u_tex, uv).rgb;" "\n" + " return col;" "\n" + "}" "\n" + "void main() {" "\n" + " vec3 c = vec3(0);" "\n" + " c += 0.0020 * get_col(v_uv + vec2(0, -4.0 / 512.0));" "\n" + " c += 0.0060 * get_col(v_uv + vec2(0, -3.0 / 512.0));" "\n" + " c += 0.0606 * get_col(v_uv + vec2(0, -2.0 / 512.0));" "\n" + " c += 0.2417 * get_col(v_uv + vec2(0, -1.0 / 512.0));" "\n" + " c += 0.3829 * get_col(v_uv + vec2(0, +0.0 / 512.0));" "\n" + " c += 0.2417 * get_col(v_uv + vec2(0, +1.0 / 512.0));" "\n" + " c += 0.0606 * get_col(v_uv + vec2(0, +2.0 / 512.0));" "\n" + " c += 0.0060 * get_col(v_uv + vec2(0, +3.0 / 512.0));" "\n" + " c += 0.0020 * get_col(v_uv + vec2(0, +4.0 / 512.0));" "\n" + " fragcolor = vec4(c, 1);" "\n" + "}" "\n" + , NULL); + + if(!f) return 0; + + minibloom2 = k3ProgramGLSL(v, f, NULL); + + if(!minibloom2) return 0; + + return 1; +} + +int make_tonemapper() { + struct k3GLSLV *v = !k3IsCore ? k3ShaderGLSLV( + "varying vec2 v_uv;" "\n" + "void main() {" "\n" + " v_uv = gl_Vertex.xy * 0.5 + 0.5;" "\n" + " gl_Position = vec4(gl_Vertex.xy, 0, 1);" "\n" + "}" "\n" + , NULL) : k3ShaderGLSLV( + "#version 330\n" + "const vec4 positions[3] = vec4[3] (vec4(-1, -1, 0, 1), vec4(3, -1, 0, 1), vec4(-1, 3, 0, 1));\n" + "out vec2 v_uv;" "\n" + "void main() {" "\n" + " v_uv = positions[gl_VertexID].xy * 0.5 + 0.5;" "\n" + " gl_Position = positions[gl_VertexID];" "\n" + "}" "\n" + , NULL); + + if(!v) return 0; + + struct k3GLSLF *f = k3ShaderGLSLF( + "uniform sampler2D u_tex;" "\n" + "varying vec2 v_uv;" "\n" + "const vec3 WHITE = vec3(0.95045592705, 1.0, 1.08905775076);" "\n" + "const mat3 RGB_2_XYZ = (mat3(" "\n" + " 0.4124564, 0.2126729, 0.0193339," "\n" + " 0.3575761, 0.7151522, 0.1191920," "\n" + " 0.1804375, 0.0721750, 0.9503041" "\n" + "));" "\n" + "const mat3 XYZ_2_RGB = (mat3(" "\n" + " 3.2404542,-0.9692660, 0.0556434," "\n" + " -1.5371385, 1.8760108,-0.2040259," "\n" + " -0.4985314, 0.0415560, 1.0572252" "\n" + "));" "\n" + "float XYZ_TO_LAB_F(float x) {" + " return x > 0.00885645167 ? pow(x, 0.333333333) : 7.78703703704 * x + 0.13793103448;" + "}" + "vec3 XYZ_TO_LAB(vec3 xyz) {" + " vec3 xyz_scaled = xyz / WHITE;" + " xyz_scaled = vec3(" + " XYZ_TO_LAB_F(xyz_scaled.x)," + " XYZ_TO_LAB_F(xyz_scaled.y)," + " XYZ_TO_LAB_F(xyz_scaled.z)" + " );" + " return vec3(" + " (1.16 * xyz_scaled.y) - 0.16," + " 5.0 * (xyz_scaled.x - xyz_scaled.y)," + " 2.0 * (xyz_scaled.y - xyz_scaled.z)" + " );" + "}" + "float LAB_TO_XYZ_F(float x) {" + " return (x > 0.206897) ? x * x * x : (0.12841854934 * (x - 0.137931034));" + "}" + "vec3 LAB_TO_XYZ(vec3 Lab) {" + " float w = (Lab.x + 0.16) / 1.16;" + " return WHITE * vec3(" + " LAB_TO_XYZ_F(w + Lab.y / 5.0)," + " LAB_TO_XYZ_F(w)," + " LAB_TO_XYZ_F(w - Lab.z / 2.0)" + " );" + "}" + "vec3 toner(vec3 col, float whitepoint, float saturation) {" "\n" + " vec3 xyz = RGB_2_XYZ * col;" "\n" + " vec3 lab = XYZ_TO_LAB(xyz);" "\n" + " lab.yz *= saturation;" "\n" + " float n = lab.x * (1.0 + (lab.x / (whitepoint * whitepoint)));" "\n" + " lab.x = n / (1.0 + lab.x);" "\n" + " xyz = LAB_TO_XYZ(lab);" "\n" + " return XYZ_2_RGB * xyz;" "\n" + "}" "\n" + "vec3 get_col(vec2 uv) {" "\n" + " vec3 col = texture2D(u_tex, uv).rgb;" "\n" + " col = toner(col, 0.80, 1.5);" "\n" + " return col;" "\n" + "}" "\n" + "void main() {" "\n" + " gl_FragColor = vec4(get_col(v_uv), 1);" "\n" + "}" "\n" + , NULL); + + if(!f) return 0; + + tonemapper = k3ProgramGLSL(v, f, NULL); + + if(!tonemapper) return 0; + + return 1; +} + +int k3BloomInit() { + if(!k3IsCore && !GLAD_GL_ARB_shading_language_100) { + return 0; + } + + if(!make_mb1()) return 0; + if(!make_mb2()) return 0; + if(!make_tonemapper()) return 0; + + return 1; +} + +int k3Bloom(struct k3Offscreen *a, struct k3Offscreen *b) { + if(!minibloom1) return 0; + if(!minibloom2) return 0; + + assert(b); + + k3BeginOffscreen(b); + k3BlitToScreenEffect(a, false, k3_GLSL, minibloom1, NULL); + k3EndOffscreen(b); + + k3BlitToScreenEffect(b, true, k3_GLSL, minibloom2, NULL); + + return 1; +} + +struct k3GLSLP *k3ToneMapper() { + return tonemapper; +} diff --git a/src/k3bloom.h b/src/k3bloom.h new file mode 100644 index 0000000..4f57819 --- /dev/null +++ b/src/k3bloom.h @@ -0,0 +1,8 @@ +#pragma once + +struct k3Offscreen; + +int k3BloomInit(); +int k3Bloom(struct k3Offscreen *a, struct k3Offscreen *b); + +struct k3GLSLP *k3ToneMapper(); diff --git a/src/k3font.c b/src/k3font.c new file mode 100644 index 0000000..8538c83 --- /dev/null +++ b/src/k3font.c @@ -0,0 +1,150 @@ +#include"k3font.h" + +#include"k3batch.h" +#include +#include"gl.h" + +struct k3Font *k3FontCreate() { + struct k3Font *ret = calloc(sizeof(*ret), 1); + return ret; +} + +static int cmpglyph(const void *a, const void *b) { + return *(const uint32_t*) a - *(const uint32_t*) b; +} + +int k3FontLoad(struct k3Font *this, const uint8_t *buf, size_t len, k3FontTexLoader texldr) { + if(*(uint32_t*) buf != 0x03464D42) { + return 0; + } + + const uint8_t *end = buf + len; + + buf += 4; + + uint16_t pages = 0; + + while(buf + 5 < end) { + uint8_t blockType = *buf; + uint32_t blockSize = *(uint32_t*) (buf + 1); + buf += 5; + + if(blockType == 1) { //Info block + buf += 14; + while(*buf) buf++; + buf++; + } else if(blockType == 2) { //Common block + this->lineScale = 1.f / *(uint16_t*) buf; + buf += 2; + this->baseline = *(uint16_t*) buf; + buf += 2; + this->texW = *(uint16_t*) buf; + buf += 2; + this->texH = *(uint16_t*) buf; + buf += 2; + pages = *(uint16_t*) buf; + buf += 7; + } else if(blockType == 3) { //Pages block + if(pages == 0) return 0; + + this->pageCount = pages; + this->pages = malloc(sizeof(*this->pages) * this->pageCount); + + for(size_t i = 0; i < this->pageCount; i++) { + this->pages[i] = texldr(this, buf); + buf += strlen(buf) + 1; + } + } else if(blockType == 4) { //Chars block + size_t num = blockSize / 20; + + this->glyphs = calloc(sizeof(*this->glyphs), this->glyphCount = num); + + memcpy(this->glyphs, buf, num * 20); + + qsort(this->glyphs, num, sizeof(*this->glyphs), cmpglyph); + + buf += blockSize; + } else if(blockType == 5) { //Kerning block + // Ignore kerning for now + buf += blockSize; + } + } + + return 1; +} + +static uint32_t read_utf8(const char **txt_) { + const uint8_t **txt = (void*) txt_; + if(**txt == 0) { + return 0; + } else if(**txt < 0x80) { + return *(*txt)++; + } else if(**txt < 0xE0) { + uint32_t ret = ((*(*txt)++) & 0x1F) << 6; + ret |= (*(*txt)++) & 0x3F; + return ret; + } else if(**txt < 0xF0) { + uint32_t ret = ((*(*txt)++) & 0x0F) << 12; + ret |= ((*(*txt)++) & 0x3F) << 6; + ret |= (*(*txt)++) & 0x3F; + return ret; + } else { + uint32_t ret = ((*(*txt)++) & 0x07) << 18; + ret |= ((*(*txt)++) & 0x3F) << 12; + ret |= ((*(*txt)++) & 0x3F) << 6; + ret |= (*(*txt)++) & 0x3F; + return ret; + } +} + +void k3FontSz(struct k3Font *this, float sz, const char *txt, struct k3RectF *ret) { + float x = 0, y = 0; + while(1) { + uint32_t cp = read_utf8(&txt); + + if(!cp) break; + + struct k3FontGlyph *g = k3FontGetGlyph(this, cp); + + if(!g) continue; + + x += g->xadvance * this->lineScale * sz; + } + ret->w = x; +} +void k3FontDraw(struct k3Font *this, float xStart, float yStart, float sz, const char *txt, vec4 color) { + float x = xStart, y = yStart; + while(1) { + uint32_t cp = read_utf8(&txt); + + if(!cp) break; + + if(cp == 10) { + x = xStart; + y -= sz; + } + + struct k3FontGlyph *g = k3FontGetGlyph(this, cp); + + if(!g) continue; + + struct k3Tex *tex = this->pages[g->page]; + size_t texW = this->texW; + size_t texH = this->texH; + k3BatchAdd(tex, + (struct k3RectF) {(float) g->x / texW, (float) g->y / texH, (float) g->width / texW, (float) g->height / texH}, + (struct k3RectF) { + x + g->xoffset * this->lineScale * sz, + y + ((-g->height - g->yoffset) * this->lineScale + 1) * sz, + g->width * this->lineScale * sz, + g->height * this->lineScale * sz + }, 0, color); + + x += g->xadvance * this->lineScale * sz; + } + k3BatchFlush(); +} + +struct k3FontGlyph *k3FontGetGlyph(struct k3Font *this, uint32_t cp) { + return bsearch(&cp, this->glyphs, this->glyphCount, sizeof(*this->glyphs), cmpglyph); +} diff --git a/src/k3font.h b/src/k3font.h new file mode 100644 index 0000000..b1dc163 --- /dev/null +++ b/src/k3font.h @@ -0,0 +1,77 @@ +#pragma once + +#include"k3.h" +#include"k3batch.h" +#include + +struct k3FontGlyph { + uint32_t cp; + uint16_t x; + uint16_t y; + uint16_t width; + uint16_t height; + int16_t xoffset; + int16_t yoffset; + uint16_t xadvance; + uint8_t page; + uint8_t chnl; +}; + +struct k3Font { + void *ud; + + float lineScale; + + uint16_t baseline; + + uint16_t texW, texH; + + size_t glyphCount; + struct k3FontGlyph *glyphs; + + size_t pageCount; + struct k3Tex **pages; +}; + +typedef struct k3Tex*(*k3FontTexLoader)(struct k3Font*, const char *name); + +struct k3Font *k3FontCreate(); +int k3FontLoad(struct k3Font*, const uint8_t *buf, size_t len, k3FontTexLoader); + +void k3FontSz(struct k3Font*, float sz, const char *txt, struct k3RectF *ret); +void k3FontDraw(struct k3Font*, float x, float y, float sz, const char *txt, vec4 color); + +struct k3FontGlyph *k3FontGetGlyph(struct k3Font*, uint32_t cp); + +static inline int k3UTF8Encode(uint32_t cp, uint8_t ret[static 4]) { + if(cp < 0x80) { + ret[0] = cp; + return 1; + } else if(cp < 0x800) { + ret[0] = 192 | (cp >> 6); + ret[1] = 128 | (cp & 63); + return 2; + } else if(cp < 0x10000) { + ret[0] = 224 | (cp >> 12); + ret[1] = 128 | ((cp >> 6) & 63); + ret[2] = 128 | (cp & 63); + return 3; + } else if(cp < 0x110000) { + ret[0] = 240 | (cp >> 18); + ret[1] = 128 | ((cp >> 12) & 63); + ret[2] = 128 | ((cp >> 6) & 63); + ret[3] = 128 | (cp & 63); + return 4; + } + return 0; +} + +static inline const char *k3UTF8LastCodepointZ(const char *txt) { + size_t len = strlen(txt); + while(len--) { + if((txt[len] & 0xC0) != 0x80) { + return &txt[len]; + } + } + return NULL; +} \ No newline at end of file diff --git a/src/k3menu.c b/src/k3menu.c new file mode 100644 index 0000000..0ebcee3 --- /dev/null +++ b/src/k3menu.c @@ -0,0 +1,352 @@ +#include"k3menu.h" + +#include +#include +#include + +void k3MenuSetBounds_(struct k3MObj *this, int16_t x, int16_t y, int16_t w, int16_t h) { + this->x = x; + this->y = y; + this->w = w; + this->h = h; +} + +static bool event_send_nonrecur(struct k3MEvent *ev) { + bool handled = false; + for(size_t i = 0; i < ev->target->handlerCount; i++) { + struct k3MEventHandler *h = &ev->target->handlers[i]; + + if(h->code == ev->code || h->code == k3M_EVENT_ALL) { + if(h->callback(ev, h->ud)) { + return true; + } + } + } + + return handled; +} + +int k3MRegisterEventHandler_(struct k3MObj *obj, uint16_t evcode, k3MEventHandlerFunc callback, uint8_t *ud, size_t udSize) { + if(ud && udSize > k3M_USERDATA_SIZE) { + return 0; + } + + obj->handlers = realloc(obj->handlers, sizeof(*obj->handlers) * ++obj->handlerCount); + + obj->handlers[obj->handlerCount - 1].code = evcode; + obj->handlers[obj->handlerCount - 1].callback = callback; + if(ud) { + memcpy(obj->handlers[obj->handlerCount - 1].ud, ud, udSize); + } +} + +bool k3MEventSend(struct k3MEvent *ev) { + while(!event_send_nonrecur(ev)) { + if(ev->kind == k3M_EVENTKIND_DIRECT) { + return false; + } else if(ev->kind == k3M_EVENTKIND_BUBBLE) { + ev->target = ev->target->parent; + + if(ev->target == ev->original || !ev->target) { + return false; + } + } else if(ev->kind == k3M_EVENTKIND_CAPTURE) { + struct k3MObj *parent = ev->target; + + for(size_t i = 0; i < parent->childCount; i++) { + if(!parent->children[i]->invisible) { + ev->target = parent->children[i]; + + if(k3MEventSend(ev)) { + return true; + } + } + } + + return false; + } + } + + return true; +} + +static bool label_draw(struct k3MEvent *ev, uint8_t *ud) { + struct k3MLabel *this = (void*) ev->target; + + if(this->txt) { + k3FontDraw(this->font, this->x, this->y, this->sz, this->txt, (vec4) {1, 1, 1, 1}); + } + + return false; +} + +struct k3MLabel *k3MLabel(struct k3Font *font, float sz, const char *txt) { + struct k3MLabel *ret = calloc(1, sizeof(*ret)); + ret->font = font; + ret->sz = sz; + ret->txt = txt; + + k3MRegisterEventHandler(ret, k3M_EVENT_DRAW, label_draw, NULL, 0); + + return ret; +} + +int k3MRemoveChild(struct k3MObj *parent, struct k3MObj *child) { + for(size_t i = 0; i < parent->childCount; i++) { + if(parent->children[i] == child) { + memcpy(&parent->children[i], &parent->children[i + 1], sizeof(*parent->children) * (parent->childCount - i - 1)); + + parent->childCount--; + + child->parent = NULL; + + return 1; + } + } + + return 0; +} + +void k3MAddChild(struct k3MObj *parent, struct k3MObj *child) { + if(child->parent) { + k3MRemoveChild(child->parent, child); + } + + parent->children = realloc(parent->children, sizeof(*parent->children) * (++parent->childCount)); + parent->children[parent->childCount - 1] = child; + + child->parent = parent; +} + +/*static struct k3Menu *k3ScreenFuncMouse(struct k3Menu *_, int16_t x, int16_t y, int ev) { + struct k3Screen *this = (void*) _; + + if(ev == k3_MENU_MOUSE_MOVE) { + struct k3Menu *newHovered = k3ContainerFuncMouse(_, x, y, k3_MENU_MOUSE_TEST); + if(this->hovered != newHovered) { + if(this->hovered && this->hovered->funcmouse) { + this->hovered->funcmouse(this->hovered, x, y, k3_MENU_MOUSE_OUT); + } + + this->hovered = newHovered; + + if(this->hovered && this->hovered->funcmouse) { + this->hovered->funcmouse(this->hovered, x, y, k3_MENU_MOUSE_IN); + } + } else { + if(this->hovered && this->hovered->funcmouse) { + return this->hovered->funcmouse(this->hovered, x, y, k3_MENU_MOUSE_MOVE); + } + } + } else if(this->hovered && this->hovered->funcmouse) { + struct k3Menu *ret = this->hovered->funcmouse(this->hovered, x, y, ev); + this->keyboardFocus = ret; + return ret; + } + + return NULL; +} + +static void k3ScreenFuncKey(struct k3Menu *_, int key, int action, int modifiers) { + struct k3Screen *this = (void*) _; + + if(this->keyboardFocus && this->keyboardFocus->funckey) { + this->keyboardFocus->funckey(this->keyboardFocus, key, action, modifiers); + } +} + +static void k3ScreenFuncChar(struct k3Menu *_, uint32_t codepoint) { + struct k3Screen *this = (void*) _; + + if(this->keyboardFocus && this->keyboardFocus->funcchar) { + this->keyboardFocus->funcchar(this->keyboardFocus, codepoint); + } +}*/ + +static bool screen_ev(struct k3MEvent *ev, uint8_t *ud) { + struct k3MScreen *this = (void*) ev->target; + + if(ev->code == k3M_EVENT_MOUSE_PRESS || ev->code == k3M_EVENT_MOUSE_RELEASE || ev->code == k3M_EVENT_MOUSE_MOTION) { + struct k3MObj *innermost = (void*) this; + + while(innermost->childCount != 0) { + + bool foundInner = false; + + for(intmax_t i = innermost->childCount - 1; i >= 0; i--) { + + struct k3MObj *child = innermost->children[i]; + + if(!child->invisible && ev->mouse.x >= child->x && ev->mouse.x < child->x + child->w && ev->mouse.y >= child->y && ev->mouse.y < child->y + child->h) { + + innermost = child; + foundInner = true; + + break; + + } + + } + + if(!foundInner) { + break; + } + + } + + if(innermost != (void*) this) { + + if(ev->code == k3M_EVENT_MOUSE_PRESS) { + this->keyboardFocus = innermost; + } + + ev->kind = k3M_EVENTKIND_BUBBLE; + ev->target = innermost; + k3MEventSend(ev); + + if(ev->code == k3M_EVENT_MOUSE_RELEASE) { + ev->code = k3M_EVENT_MOUSE_CLICK; + + ev->kind = k3M_EVENTKIND_DIRECT; + ev->target = innermost; + k3MEventSend(ev); + } + + } + + return true; + } else if(ev->code == k3M_EVENT_KEY_PRESS || ev->code == k3M_EVENT_KEY_RELEASE || ev->code == k3M_EVENT_INPUT) { + + if(this->keyboardFocus) { + ev->kind = k3M_EVENTKIND_BUBBLE; + ev->target = (void*) this->keyboardFocus; + k3MEventSend(ev); + } + + return true; + + } + + ev->kind = k3M_EVENTKIND_CAPTURE; + for(intmax_t i = this->childCount - 1; i >= 0; i--) { + if(!this->children[i]->invisible) { + ev->target = this->children[i]; + if(k3MEventSend(ev)) { + return true; + } + } + } + + return false; +} +struct k3MScreen *k3MScreen() { + struct k3MScreen *ret = calloc(1, sizeof(*ret)); + + k3MRegisterEventHandler(ret, k3M_EVENT_ALL, screen_ev, NULL, 0); + + return ret; +} + +static bool textbutton_draw(struct k3MEvent *ev, uint8_t *ud) { + struct k3MTextButton *this = (void*) ev->target; + + if(this->txt) { + k3BatchAdd(NULL, (struct k3RectF) {}, (struct k3RectF) { + this->x, this->y, + this->w, this->h + }, 0, (vec4) {1, 1, 1, 0.2 + 0.1 * this->hovered}); + + struct k3RectF txtsz; + k3FontSz(this->font, this->sz, this->txt, &txtsz); + + k3FontDraw(this->font, this->x + this->w / 2 - txtsz.w / 2, this->y, this->sz, this->txt, (vec4) { + 1 - 0.8 * this->disabled, 1 - 0.8 * this->disabled, 1 - 0.8 * this->disabled, 1}); + } + + return false; +} +struct k3MTextButton *k3MTextButton(struct k3Font *font, float sz, const char *txt) { + struct k3MTextButton *ret = calloc(1, sizeof(*ret)); + + ret->font = font; + ret->sz = sz; + ret->txt = txt; + + k3MRegisterEventHandler(ret, k3M_EVENT_DRAW, textbutton_draw, NULL, 0); + + return ret; +} + +static bool textinput_draw(struct k3MEvent *ev, uint8_t *ud) { + struct k3MTextInput *this = (void*) ev->target; + + if(this->txt) { + k3BatchAdd(NULL, (struct k3RectF) {}, (struct k3RectF) { + this->x, this->y, + this->w, this->h + }, 0, (vec4) {1, 1, 1, 0.2}); + + int isPlaceholder = strlen(this->txt) == 0; + const char *txt = isPlaceholder ? this->placeholder : this->txt; + + struct k3RectF txtsz; + k3FontSz(this->font, this->sz, txt, &txtsz); + + k3FontDraw(this->font, this->x, this->y, this->sz, txt, (vec4) {1 - 0.5 * isPlaceholder, 1 - 0.5 * isPlaceholder, 1 - 0.5 * isPlaceholder, 1}); + } + + return false; +} +static bool textinput_key(struct k3MEvent *ev, uint8_t *ud) { + struct k3MTextInput *this = (void*) ev->target; + + if(ev->key.num == GLFW_KEY_BACKSPACE && ev->code != k3M_EVENT_KEY_RELEASE) { + char *last = k3UTF8LastCodepointZ(this->txt); + if(last) { + *last = 0; + } + } else if(ev->key.num == GLFW_KEY_ENTER && ev->code == k3M_EVENT_KEY_PRESS) { + struct k3MEvent newev = { + .code = k3M_EVENT_COMPLETE, + .kind = k3M_EVENTKIND_DIRECT, + + .original = (void*) this, + .target = (void*) this, + }; + + k3MEventSend(&newev); + } + + return true; +} +static bool textinput_char(struct k3MEvent *ev, uint8_t *ud) { + struct k3MTextInput *this = (void*) ev->target; + + size_t len = strlen(this->txt); + + this->txt = realloc(this->txt, len + 5); + len += k3UTF8Encode(ev->input.code, this->txt + len); + this->txt[len] = 0; + + return true; +} +struct k3MTextInput *k3MTextInput(struct k3Font *font, float sz, const char *placeholder, const char *txt) { + struct k3MTextInput *ret = calloc(1, sizeof(*ret)); + + ret->font = font; + ret->sz = sz; + ret->placeholder = placeholder ? placeholder : ""; + ret->txt = strdup(txt ? txt : ""); + + k3MRegisterEventHandler(ret, k3M_EVENT_DRAW, textinput_draw, NULL, 0); + k3MRegisterEventHandler(ret, k3M_EVENT_KEY_PRESS, textinput_key, NULL, 0); + k3MRegisterEventHandler(ret, k3M_EVENT_KEY_RELEASE, textinput_key, NULL, 0); + k3MRegisterEventHandler(ret, k3M_EVENT_INPUT, textinput_char, NULL, 0); + + return ret; +} + +struct k3MObj *k3MObj() { + struct k3MObj *ret = calloc(1, sizeof(*ret)); + return ret; +} diff --git a/src/k3menu.h b/src/k3menu.h new file mode 100644 index 0000000..f50f391 --- /dev/null +++ b/src/k3menu.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include +#include"k3font.h" + +struct k3MObj; + +#define k3M_EVENT_MOUSE_ENTER 0 +#define k3M_EVENT_MOUSE_MOTION 1 +#define k3M_EVENT_MOUSE_LEAVE 2 +#define k3M_EVENT_MOUSE_PRESS 3 +#define k3M_EVENT_MOUSE_RELEASE 4 +#define k3M_EVENT_MOUSE_CLICK 5 +#define k3M_EVENT_KEY_PRESS 6 +#define k3M_EVENT_KEY_RELEASE 7 +#define k3M_EVENT_INPUT 8 +#define k3M_EVENT_DRAW 9 +#define k3M_EVENT_COMPLETE 10 +#define k3M_EVENT_ALL 11 + +#define k3M_MOUSE_BUTTON_0 0 +#define k3M_MOUSE_BUTTON_1 1 +#define k3M_MOUSE_BUTTON_2 2 + +#define k3M_EVENTKIND_DIRECT 0 +#define k3M_EVENTKIND_CAPTURE 1 +#define k3M_EVENTKIND_BUBBLE 2 + +#define k3M_USERDATA_SIZE 16 + +struct k3MEvent { + uint16_t code; + + uint8_t kind; + + struct k3MObj *original; + struct k3MObj *target; + + union { + struct { + int button; + int16_t x; + int16_t y; + } mouse; + struct { + uint32_t num; + } key; + struct { + uint32_t code; + } input; + }; +}; + +typedef bool(*k3MEventHandlerFunc)(struct k3MEvent*, uint8_t *ud); + +typedef struct k3MEventHandler { + uint8_t ud[k3M_USERDATA_SIZE]; + k3MEventHandlerFunc callback; + uint16_t code; +} k3MEventHandler; + +struct k3MObj { + struct k3MObj *parent; + + size_t childCount; + struct k3MObj **children; + + int16_t x; + int16_t y; + int16_t w; + int16_t h; + + bool invisible, hovered, disabled; + + k3MEventHandler *handlers; + size_t handlerCount; +}; + +#define k3MenuSetBounds(a, x, y, w, h) k3MenuSetBounds_((struct k3MObj*) (a), (x), (y), (w), (h)) +void k3MenuSetBounds_(struct k3MObj *this, int16_t x, int16_t y, int16_t w, int16_t h); + +#define k3MRegisterEventHandler(a, c, cb, ud, udsz) k3MRegisterEventHandler_((struct k3MObj*) (a), (c), (cb), (ud), (udsz)) +int k3MRegisterEventHandler_(struct k3MObj *obj, uint16_t evcode, k3MEventHandlerFunc callback, uint8_t *ud, size_t udSize); + +bool k3MEventSend(struct k3MEvent *ev); + +int k3MRemoveChild(struct k3MObj *parent, struct k3MObj *child); +void k3MAddChild(struct k3MObj *parent, struct k3MObj *child); + +struct k3MLabel { + struct k3MObj; + + struct k3Font *font; + float sz; + char *txt; +}; +struct k3MLabel *k3MLabel(struct k3Font *font, float sz, const char *txt); + +struct k3MScreen { + struct k3MObj; + + struct k3MObj *keyboardFocus; +}; +struct k3MScreen *k3MScreen(); + +struct k3MTextButton { + struct k3MObj; + + struct k3Font *font; + float sz; + char *txt; +}; +struct k3MTextButton *k3MTextButton(struct k3Font *font, float sz, const char *txt); + +struct k3MTextInput { + struct k3MObj; + + struct k3Font *font; + float sz; + + char *placeholder; + + char *txt; +}; +struct k3MTextInput *k3MTextInput(struct k3Font *font, float sz, const char *placeholder, const char *txt); diff --git a/src/k3water.c b/src/k3water.c new file mode 100644 index 0000000..64d02cb --- /dev/null +++ b/src/k3water.c @@ -0,0 +1,150 @@ +#include"k3water.h" + +#include +#include + +struct k3Water *k3WaterCreate(float w, float h, size_t subdivisions, struct k3Mat *mat) { + size_t edgesubdiv = 2 + subdivisions; + + size_t vertices = edgesubdiv * edgesubdiv; + + struct k3Water *ret = malloc(sizeof(*ret)); + + ret->w = w; + ret->h = h; + + for(int f = 0; f < k3_WATER_FRAMES; f++) { + ret->frames[f] = malloc(sizeof(vec3) * vertices); + } + + ret->curFrame = 0; + + ret->dx = w / (edgesubdiv - 1); + ret->dy = h / (edgesubdiv - 1); + + ret->ptsPerEdge = edgesubdiv; + + for(int z = 0; z < edgesubdiv; z++) { + for(int x = 0; x < edgesubdiv; x++) { + ret->frames[0][z * edgesubdiv + x][0] = x * w / (edgesubdiv - 1) - w / 2; + ret->frames[0][z * edgesubdiv + x][1] = 0; + ret->frames[0][z * edgesubdiv + x][2] = z * h / (edgesubdiv - 1) - h / 2; + } + } + memcpy(ret->frames[1], ret->frames[0], sizeof(vec3) * vertices); + memcpy(ret->frames[2], ret->frames[0], sizeof(vec3) * vertices); + + size_t indices = (1 + subdivisions) * (1 + subdivisions) * 6; + uint16_t *inds = malloc(sizeof(*inds) * indices); + + for(int z = 0; z <= subdivisions; z++) { + for(int x = 0; x <= subdivisions; x++) { + inds[6 * (z * (subdivisions + 1) + x) + 0] = (z * edgesubdiv + x) + 0; + inds[6 * (z * (subdivisions + 1) + x) + 1] = (z * edgesubdiv + x) + edgesubdiv; + inds[6 * (z * (subdivisions + 1) + x) + 2] = (z * edgesubdiv + x) + edgesubdiv + 1; + inds[6 * (z * (subdivisions + 1) + x) + 3] = (z * edgesubdiv + x) + 0; + inds[6 * (z * (subdivisions + 1) + x) + 4] = (z * edgesubdiv + x) + edgesubdiv + 1; + inds[6 * (z * (subdivisions + 1) + x) + 5] = (z * edgesubdiv + x) + 1; + } + } + + uint8_t *norms = alloca(ret->ptsPerEdge * ret->ptsPerEdge * sizeof(*norms) * 3); + + ret->mdl = k3MdlCreate(vertices, indices, 0, ret->frames[0], norms, NULL, NULL, NULL, NULL, inds, NULL, NULL); + + k3MdlAddMesh(ret->mdl, mat, 0, indices); + + free(inds); + + return ret; +} + +static void k3WaterSimSubStep(struct k3Water *this, float dt, float C, float mu) { + int prevPrevFrame = (this->curFrame + (k3_WATER_FRAMES - 1)) % k3_WATER_FRAMES; + int prevFrame = this->curFrame; + this->curFrame = (this->curFrame + 1) % k3_WATER_FRAMES; + + vec3 *prevPrev = this->frames[prevPrevFrame]; + vec3 *prev = this->frames[prevFrame]; + vec3 *cur = this->frames[this->curFrame]; + + for(int z = 1; z < this->ptsPerEdge - 1; z++) { + for(int x = 1; x < this->ptsPerEdge - 1; x++) { + cur[z * this->ptsPerEdge + x][1] = + + 4 * prev[z * this->ptsPerEdge + x][1] / (2 + mu * dt) + - prevPrev[z * this->ptsPerEdge + x][1] * (2 - mu * dt) / (2 + mu * dt) + + 2 * C * C * dt * dt * ( + (prev[z * this->ptsPerEdge + x + 1][1] - 2 * prev[z * this->ptsPerEdge + x][1] + prev[z * this->ptsPerEdge + x - 1][1]) / (this->dx * this->dx) + + (prev[(z + 1) * this->ptsPerEdge + x][1] - 2 * prev[z * this->ptsPerEdge + x][1] + prev[(z - 1) * this->ptsPerEdge + x][1]) / (this->dy * this->dy) + ); + } + } + + for(int x = 1; x < this->ptsPerEdge - 1; x++) { + cur[0 * this->ptsPerEdge + x][1] = 0; + cur[(this->ptsPerEdge - 1) * this->ptsPerEdge + x][1] = 0; + } + + for(int z = 0; z < this->ptsPerEdge; z++) { + cur[z * this->ptsPerEdge + 0][1] = 0; + cur[z * this->ptsPerEdge + this->ptsPerEdge - 1][1] = 0; + } +} + +void k3WaterSimulate(struct k3Water *this, float dt, float C, float mu, int substeps) { + for(int i = 0; i < substeps; i++) { + k3WaterSimSubStep(this, dt / substeps, C, mu); + } +} + +void k3WaterUpdate(struct k3Water *this) { + k3MdlUpdatePos(this->mdl, this->frames[this->curFrame]); + + typedef int8_t Nrm[3]; + + Nrm *nrm = alloca(this->ptsPerEdge * this->ptsPerEdge * sizeof(*nrm)); + + for(size_t i = 0; i < this->ptsPerEdge; i++) { + nrm[i * this->ptsPerEdge + 0][0] = 0; + nrm[i * this->ptsPerEdge + 0][1] = 1; + nrm[i * this->ptsPerEdge + 0][2] = 0; + + nrm[i * this->ptsPerEdge + this->ptsPerEdge - 1][0] = 0; + nrm[i * this->ptsPerEdge + this->ptsPerEdge - 1][1] = 1; + nrm[i * this->ptsPerEdge + this->ptsPerEdge - 1][2] = 0; + + nrm[0 * this->ptsPerEdge + i][0] = 0; + nrm[0 * this->ptsPerEdge + i][1] = 1; + nrm[0 * this->ptsPerEdge + i][2] = 0; + + nrm[(this->ptsPerEdge - 1) * this->ptsPerEdge + i][0] = 0; + nrm[(this->ptsPerEdge - 1) * this->ptsPerEdge + i][1] = 1; + nrm[(this->ptsPerEdge - 1) * this->ptsPerEdge + i][2] = 0; + } + + for(size_t z = 1; z < this->ptsPerEdge - 1; z++) { + for(size_t x = 1; x < this->ptsPerEdge - 1; x++) { + vec3 n = { + (this->frames[this->curFrame][z * this->ptsPerEdge + x + 1][1] - this->frames[this->curFrame][z * this->ptsPerEdge + x - 1][1]) / 2, + 1, + (this->frames[this->curFrame][(z + 1) * this->ptsPerEdge + x][1] - this->frames[this->curFrame][(z - 1) * this->ptsPerEdge + x][1]) / 2, + }; + + glm_vec3_normalize(n); + + nrm[z * this->ptsPerEdge + x][0] = n[0] * 127; + nrm[z * this->ptsPerEdge + x][1] = n[1] * 127; + nrm[z * this->ptsPerEdge + x][2] = n[2] * 127; + } + } + + k3MdlUpdateNrm(this->mdl, nrm); +} + +void k3WaterDisturb(struct k3Water *this, float x, float y, float radius, float powr) { + int i = (x + this->w / 2) * this->ptsPerEdge / this->w; + int j = (y + this->h / 2) * this->ptsPerEdge / this->h; + + this->frames[this->curFrame][j * this->ptsPerEdge + i][1] += -powr; + this->frames[(this->curFrame + 2) % k3_WATER_FRAMES][j * this->ptsPerEdge + i][1] += -powr; +} diff --git a/src/k3water.h b/src/k3water.h new file mode 100644 index 0000000..7a54154 --- /dev/null +++ b/src/k3water.h @@ -0,0 +1,42 @@ +#pragma once + +#include"k3.h" + +enum k3WaterObstacleType { + k3_WATER_OBSTACLE_CYLINDER, + k3_WATER_OBSTACLE_RECTCUBOID +}; + +struct k3WaterObstacle { + enum k3WaterObstacleType type; + float depth; + float x; + float y; + union { + float radius; + float width; + }; + float height; +}; + +struct k3Water { + struct k3Mdl *mdl; + + float w, h; + +#define k3_WATER_FRAMES 3 + vec3 *frames[k3_WATER_FRAMES]; + int curFrame; + + float dx, dy; + + int ptsPerEdge; + + uint8_t obstacleCount, obstacleCapacity; + struct k3WaterObstacle *obstacles; +}; + +struct k3Water *k3WaterCreate(float w, float h, size_t subdivisions, struct k3Mat*); +void k3WaterSimulate(struct k3Water*, float dt, float C, float mu, int substeps); //C=speed, mu=damping +void k3WaterUpdate(struct k3Water*); +void k3WaterDisturb(struct k3Water*, float x, float y, float radius, float powr); \ No newline at end of file