4jcraft/targets/4J.Render/4J_Render.cpp

1188 lines
37 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "4J_Render.h"
#include "gl3_loader.h"
// undefine macros from header to avoid argument mismatch
#undef glGenTextures
#undef glDeleteTextures
#undef glTexImage2D
#undef glCallLists
#undef glFog
#undef glLight
#undef glLightModel
#undef glTexGen
#undef glTexCoordPointer
#undef glNormalPointer
#undef glColorPointer
#undef glVertexPointer
#undef glGenQueriesARB
#undef glGetQueryObjectuARB
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define GLM_FORCE_RADIANS
#include <dlfcn.h>
#include <pthread.h>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <unordered_map>
#include <vector>
C4JRender RenderManager;
// MARK: Shaders
#define CPP_GLSL_INCLUDE
#ifdef GLES
static const char* VERT_SRC =
#include "shaders/vertex_es.vert"
;
static const char* FRAG_SRC =
#include "shaders/fragment_es.frag"
;
#else
static const char* VERT_SRC =
#include "shaders/vertex.vert"
;
static const char* FRAG_SRC =
#include "shaders/fragment.frag"
;
#endif
#undef CPP_GLSL_INCLUDE
// MARK: OpenGL state
// Hello SDL and opengl 3.3
static SDL_Window* s_window = nullptr;
static SDL_GLContext s_glContext = nullptr;
static bool s_shouldClose = false;
static int s_windowWidth = 1920;
static int s_windowHeight = 1080;
static int s_reqWidth = 1920;
static int s_reqHeight = 1080;
static bool s_fullscreen = false;
static pthread_key_t s_glCtxKey;
static pthread_once_t s_glCtxKeyOnce = PTHREAD_ONCE_INIT;
static void makeGLCtxKey() { pthread_key_create(&s_glCtxKey, nullptr); }
static const int MAX_SHARED_CTXS = 6;
static SDL_Window* s_sharedWins[MAX_SHARED_CTXS] = {};
static SDL_GLContext s_sharedCtxs[MAX_SHARED_CTXS] = {};
static int s_sharedCtxCount = 0;
static int s_nextSharedCtx = 0;
static pthread_mutex_t s_sharedMtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t s_glCallMtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_t s_mainThread;
static bool s_mainThreadSet = false;
static thread_local bool s_rs_dirty = true;
static void onFramebufferResize(int w, int h) {
if (w < 1) w = 1;
if (h < 1) h = 1;
s_windowWidth = w;
s_windowHeight = h;
glViewport(0, 0, w, h);
}
static GLuint compileShader(GLenum type, const char* src) {
GLuint s = glCreateShader(type);
glShaderSource(s, 1, &src, nullptr);
glCompileShader(s);
GLint ok = 0;
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[1024];
glGetShaderInfoLog(s, sizeof(log), nullptr, log);
fprintf(stderr, "[4J_Render] shader error:\n%s\n", log);
glDeleteShader(s);
return 0;
}
return s;
}
static GLuint linkProgram(GLuint v, GLuint f) {
GLuint p = glCreateProgram();
glAttachShader(p, v);
glAttachShader(p, f);
glLinkProgram(p);
GLint ok = 0;
glGetProgramiv(p, GL_LINK_STATUS, &ok);
if (!ok) {
char log[1024];
glGetProgramInfoLog(p, sizeof(log), nullptr, log);
fprintf(stderr, "[4J_Render] link error:\n%s\n", log);
glDeleteProgram(p);
return 0;
}
return p;
}
// Shader struct
struct ShaderUniforms {
GLuint prog = 0;
GLint uMVP = -1, uMV = -1, uBaseColor = -1;
GLint uTexMat0 = -1;
GLint uNormalMatrix = -1, uNormalSign = -1;
GLint uLighting = -1, uLight0Dir = -1, uLight1Dir = -1;
GLint uLightDiffuse = -1, uLightAmbient = -1;
GLint uFogMode = -1, uFogStart = -1, uFogEnd = -1;
GLint uFogDensity = -1, uFogColor = -1, uFogEnable = -1;
GLint uLMTransform = -1, uUseLightmap = -1, uAlphaRef = -1;
GLint uTex0 = -1, uTex1 = -1, uGlobalLM = -1;
GLint uUseTexture = -1;
GLint uInvGamma = -1;
GLint uChunkOffset = -1;
void build(const char* vs, const char* fs) {
GLuint v = compileShader(GL_VERTEX_SHADER, vs);
GLuint f = compileShader(GL_FRAGMENT_SHADER, fs);
prog = linkProgram(v, f);
glDeleteShader(v);
glDeleteShader(f);
if (!prog) return;
#define L(x) x = glGetUniformLocation(prog, #x)
L(uMVP);
L(uMV);
L(uNormalMatrix);
L(uNormalSign);
L(uTexMat0);
L(uBaseColor);
L(uLighting);
L(uLight0Dir);
L(uLight1Dir);
L(uLightDiffuse);
L(uLightAmbient);
L(uFogMode);
L(uFogStart);
L(uFogEnd);
L(uFogDensity);
L(uFogColor);
L(uFogEnable);
L(uLMTransform);
L(uUseLightmap);
L(uAlphaRef);
L(uTex0);
L(uTex1);
L(uGlobalLM);
L(uUseTexture);
L(uInvGamma);
L(uChunkOffset);
#undef L
glUseProgram(prog);
glUniform1i(uTex0, 0);
glUniform1i(uTex1, 1);
}
} s_shader;
// Matrix stacks
static const int STACK_DEPTH = 64;
struct MatrixStack {
glm::mat4 stack[STACK_DEPTH];
int top = 0;
MatrixStack() { stack[0] = glm::mat4(1.f); }
glm::mat4& cur() { return stack[top]; }
void push() {
if (top < STACK_DEPTH - 1) {
stack[top + 1] = stack[top];
++top;
}
}
void pop() {
if (top > 0) --top;
}
void load(const glm::mat4& m) { cur() = m; }
void mul(const glm::mat4& m) { cur() = cur() * m; }
};
static thread_local MatrixStack s_proj, s_mv, s_tex[2];
static thread_local int s_matMode = 0; // 0=MV 1=proj 2=tex0 3=tex1
// cache normal matrix
static thread_local bool s_normalMatDirty = true;
static thread_local glm::mat3 s_cachedNormalMat;
static thread_local float s_cachedNormalSign = 1.0f;
static inline void markNormalDirty() { s_normalMatDirty = true; }
static MatrixStack& activeStack() {
switch (s_matMode) {
case 1:
return s_proj;
case 2:
return s_tex[0];
case 3:
return s_tex[1];
}
return s_mv;
}
static void flushMatrices() {
glm::mat4 mvp = s_proj.cur() * s_mv.cur();
glUniformMatrix4fv(s_shader.uMVP, 1, GL_FALSE, glm::value_ptr(mvp));
glUniformMatrix4fv(s_shader.uMV, 1, GL_FALSE, glm::value_ptr(s_mv.cur()));
// Send the texture matrix to the depths of hell...
glUniformMatrix4fv(s_shader.uTexMat0, 1, GL_FALSE,
glm::value_ptr(s_tex[0].cur()));
// if (s_shader.uLighting)
if (s_shader.uNormalMatrix >= 0) {
if (s_normalMatDirty) {
glm::mat3 m3 = glm::mat3(s_mv.cur());
s_cachedNormalMat = glm::transpose(glm::inverse(m3));
s_cachedNormalSign = glm::determinant(m3) < 0.0f ? -1.0f : 1.0f;
s_normalMatDirty = false;
}
glUniformMatrix3fv(s_shader.uNormalMatrix, 1, GL_FALSE,
glm::value_ptr(s_cachedNormalMat));
glUniform1f(s_shader.uNormalSign, s_cachedNormalSign);
}
}
// Render state
struct RenderState {
glm::vec4 baseColor = {1, 1, 1, 1};
glm::vec4 fogColor = {0, 0, 0, 1};
float fogStart = 0, fogEnd = 1000, fogDensity = 0;
int fogMode = 0;
bool fogEnable = false;
float alphaRef = 0.1f;
float gamma = 1.0f;
bool useTexture = true, useLightmap = false, lighting = false;
glm::vec3 l0 = {0.173913f, 0.869565f, -0.608696f};
glm::vec3 l1 = {-0.173913f, 0.869565f, 0.608696f};
glm::vec3 ldiff = {0.6f, 0.6f, 0.6f};
glm::vec3 lamb = {0.4f, 0.4f, 0.4f};
glm::vec4 lmt = {1, 1, 0, 0};
glm::vec2 globalLM = {240.f, 240.f}; // fullbright default
int activeTexture = 0;
// we MAKE sure everything is FINE.
bool operator!=(const RenderState& o) const {
return baseColor != o.baseColor || fogColor != o.fogColor ||
fogStart != o.fogStart || fogEnd != o.fogEnd ||
fogDensity != o.fogDensity || fogMode != o.fogMode ||
fogEnable != o.fogEnable || alphaRef != o.alphaRef ||
gamma != o.gamma || useTexture != o.useTexture ||
useLightmap != o.useLightmap || lighting != o.lighting ||
l0 != o.l0 || l1 != o.l1 || ldiff != o.ldiff || lamb != o.lamb ||
lmt != o.lmt || globalLM != o.globalLM ||
activeTexture != o.activeTexture;
}
};
static thread_local RenderState s_rs;
static RenderState s_gpu_state;
static bool s_gpu_state_valid = false;
// track currently bound program to avoid iggy shitting up
static GLuint s_boundProgram = 0;
static void pushRenderState() {
if (!s_shader.prog) return;
// only call glUseProgram when something actually changed the binding
if (s_boundProgram != s_shader.prog) {
glUseProgram(s_shader.prog);
s_boundProgram = s_shader.prog;
}
if (!s_gpu_state_valid || s_gpu_state != s_rs) {
glUniform4fv(s_shader.uBaseColor, 1, glm::value_ptr(s_rs.baseColor));
glUniform1i(s_shader.uLighting, s_rs.lighting ? 1 : 0);
glUniform3fv(s_shader.uLight0Dir, 1, glm::value_ptr(s_rs.l0));
glUniform3fv(s_shader.uLight1Dir, 1, glm::value_ptr(s_rs.l1));
glUniform3fv(s_shader.uLightDiffuse, 1, glm::value_ptr(s_rs.ldiff));
glUniform3fv(s_shader.uLightAmbient, 1, glm::value_ptr(s_rs.lamb));
glUniform1i(s_shader.uFogMode, s_rs.fogMode);
glUniform1f(s_shader.uFogStart, s_rs.fogStart);
glUniform1f(s_shader.uFogEnd, s_rs.fogEnd);
glUniform1f(s_shader.uFogDensity, s_rs.fogDensity);
glUniform4fv(s_shader.uFogColor, 1, glm::value_ptr(s_rs.fogColor));
glUniform1i(s_shader.uFogEnable, s_rs.fogEnable ? 1 : 0);
glUniform1i(s_shader.uUseTexture, s_rs.useTexture ? 1 : 0);
glUniform1i(s_shader.uUseLightmap, s_rs.useLightmap ? 1 : 0);
glUniform1f(s_shader.uAlphaRef, s_rs.alphaRef);
glUniform1f(s_shader.uInvGamma, 1.0f / s_rs.gamma);
glUniform4fv(s_shader.uLMTransform, 1, glm::value_ptr(s_rs.lmt));
glUniform2fv(s_shader.uGlobalLM, 1, glm::value_ptr(s_rs.globalLM));
s_gpu_state = s_rs;
s_gpu_state_valid = true;
s_rs_dirty = false;
}
flushMatrices();
}
static GLuint s_sVAO_std = 0, s_sVBO_std = 0;
static void bindStdAttribs() {
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glEnableVertexAttribArray(4);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 32, (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 32, (void*)12);
glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, 32, (void*)20);
glVertexAttribPointer(3, 3, GL_BYTE, GL_TRUE, 32, (void*)24);
glVertexAttribIPointer(4, 2, GL_SHORT, 32, (void*)28);
}
static void initStreamingVAOs() {
glGenVertexArrays(1, &s_sVAO_std);
glGenBuffers(1, &s_sVBO_std);
glBindVertexArray(s_sVAO_std);
glBindBuffer(GL_ARRAY_BUFFER, s_sVBO_std);
bindStdAttribs();
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
// Chunk buffer pool (shared, protected by s_glCallMtx)
struct ChunkDrawCall {
GLenum prim;
GLint first;
GLsizei count;
};
struct ChunkBuffer {
GLuint vbo = 0;
// each chunks has its one VAO now
GLuint vao = 0;
std::vector<ChunkDrawCall> draws;
std::vector<uint8_t> rawVerts;
bool valid = false;
bool vboReady = false;
void destroy() {
if (vbo) {
glDeleteBuffers(1, &vbo);
vbo = 0;
}
if (vao) {
glDeleteVertexArrays(1, &vao);
vao = 0;
}
draws.clear();
rawVerts.clear();
valid = false;
vboReady = false;
}
};
static std::unordered_map<int, ChunkBuffer> s_chunkPool;
static int s_nextListBase = 1;
// Per-thread recording state
static thread_local int s_recListId = -1;
static thread_local std::vector<uint8_t> s_recVerts;
static thread_local std::vector<ChunkDrawCall> s_recDraws;
// Primitive helpers
static bool isQuadPrim(int pt) {
return (pt == 0x0007 /*GL_QUADS*/ ||
pt == (int)C4JRender::PRIMITIVE_TYPE_QUAD_LIST);
}
static GLenum mapPrim(int pt) {
if (isQuadPrim(pt)) return GL_TRIANGLES;
switch (pt) {
case 0:
return GL_TRIANGLES;
case 1:
return GL_LINES;
case 2:
return GL_TRIANGLE_FAN;
case 3:
return GL_LINE_STRIP;
case 4:
return GL_TRIANGLES;
case 5:
return GL_TRIANGLE_STRIP;
case 6:
return GL_TRIANGLE_FAN;
default:
return GL_TRIANGLES;
}
}
// MARK: Renderer impl
// Initialises the renderer
void C4JRender::Initialise() {
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
fprintf(stderr, "[4J_Render] SDL_Init: %s\n", SDL_GetError());
return;
}
SDL_DisplayMode dm;
if (s_reqWidth > 0 && s_reqHeight > 0) {
s_windowWidth = s_reqWidth;
s_windowHeight = s_reqHeight;
} else if (SDL_GetCurrentDisplayMode(0, &dm) == 0) {
s_windowWidth = (int)(dm.w * 0.4f);
s_windowHeight = (int)(dm.h * 0.4f);
}
#ifdef GLES
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
#else
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
SDL_GL_CONTEXT_PROFILE_CORE);
#endif
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
Uint32 wf = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
if (s_fullscreen) wf |= SDL_WINDOW_FULLSCREEN_DESKTOP;
s_window = SDL_CreateWindow("Minecraft Console Edition",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
s_windowWidth, s_windowHeight, wf);
if (!s_window) {
fprintf(stderr, "[4J_Render] Window: %s\n", SDL_GetError());
return;
}
s_glContext = SDL_GL_CreateContext(s_window);
if (!s_glContext) {
fprintf(stderr, "[4J_Render] Context: %s\n", SDL_GetError());
return;
}
#ifndef GLES
gl3_load();
#endif
int fw, fh;
SDL_GetWindowSize(s_window, &fw, &fh);
onFramebufferResize(fw, fh);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
#ifdef GLES
glClearDepthf(1.0f);
#else
glClearDepth(1.0);
#endif
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glClearColor(0, 0, 0, 1);
glViewport(0, 0, s_windowWidth, s_windowHeight);
s_shader.build(VERT_SRC, FRAG_SRC);
initStreamingVAOs();
pthread_once(&s_glCtxKeyOnce, makeGLCtxKey);
s_mainThread = pthread_self();
s_mainThreadSet = true;
pthread_setspecific(s_glCtxKey, (void*)s_window);
SDL_GL_MakeCurrent(s_window, s_glContext);
for (int i = 0; i < MAX_SHARED_CTXS; i++) {
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
SDL_Window* w = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, 1, 1,
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
if (!w) break;
SDL_GLContext ctx = SDL_GL_CreateContext(w);
if (!ctx) {
SDL_DestroyWindow(w);
break;
}
s_sharedWins[s_sharedCtxCount] = w;
s_sharedCtxs[s_sharedCtxCount] = ctx;
s_sharedCtxCount++;
}
SDL_GL_MakeCurrent(s_window, s_glContext);
pushRenderState();
#ifdef ENABLE_VSYNC
SDL_GL_SetSwapInterval(1);
#else
SDL_GL_SetSwapInterval(0);
#endif
}
void C4JRender::InitialiseContext() {
if (!s_window) return;
pthread_once(&s_glCtxKeyOnce, makeGLCtxKey);
if (s_mainThreadSet && pthread_equal(pthread_self(), s_mainThread)) {
SDL_GL_MakeCurrent(s_window, s_glContext);
pthread_setspecific(s_glCtxKey, (void*)s_window);
return;
}
void* cp = pthread_getspecific(s_glCtxKey);
if (cp) {
SDL_GLContext ctx = (SDL_GLContext)cp;
for (int i = 0; i < s_sharedCtxCount; i++)
if (s_sharedCtxs[i] == ctx) {
SDL_GL_MakeCurrent(s_sharedWins[i], ctx);
return;
}
return;
}
pthread_mutex_lock(&s_sharedMtx);
SDL_GLContext shared = (s_nextSharedCtx < s_sharedCtxCount)
? s_sharedCtxs[s_nextSharedCtx++]
: nullptr;
pthread_mutex_unlock(&s_sharedMtx);
if (!shared) return;
for (int i = 0; i < s_sharedCtxCount; i++)
if (s_sharedCtxs[i] == shared)
SDL_GL_MakeCurrent(s_sharedWins[i], shared);
pthread_setspecific(s_glCtxKey, (void*)shared);
}
void C4JRender::StartFrame() {
int w, h;
SDL_GetWindowSize(s_window, &w, &h);
s_windowWidth = w > 0 ? w : 1;
s_windowHeight = h > 0 ? h : 1;
glViewport(0, 0, s_windowWidth, s_windowHeight);
}
void C4JRender::Present() {
if (!s_window) return;
SDL_Event ev;
while (SDL_PollEvent(&ev)) {
if (ev.type == SDL_QUIT)
s_shouldClose = true;
else if (ev.type == SDL_WINDOWEVENT) {
if (ev.window.event == SDL_WINDOWEVENT_CLOSE)
s_shouldClose = true;
else if (ev.window.event == SDL_WINDOWEVENT_RESIZED)
onFramebufferResize(ev.window.data1, ev.window.data2);
}
}
glFlush();
SDL_GL_SwapWindow(s_window);
}
void C4JRender::SetWindowSize(int w, int h) {
s_reqWidth = w;
s_reqHeight = h;
}
void C4JRender::SetFullscreen(bool fs) { s_fullscreen = fs; }
bool C4JRender::ShouldClose() { return !s_window || s_shouldClose; }
void C4JRender::GetFramebufferSize(int& w, int& h) {
w = s_windowWidth;
h = s_windowHeight;
}
void C4JRender::Close() { s_window = nullptr; }
void C4JRender::Shutdown() {
pthread_mutex_lock(&s_glCallMtx);
for (auto& kv : s_chunkPool) kv.second.destroy();
s_chunkPool.clear();
pthread_mutex_unlock(&s_glCallMtx);
glDeleteVertexArrays(1, &s_sVAO_std);
glDeleteBuffers(1, &s_sVBO_std);
if (s_shader.prog) glDeleteProgram(s_shader.prog);
if (s_glContext) {
SDL_GL_DeleteContext(s_glContext);
s_glContext = nullptr;
}
if (s_window) {
SDL_DestroyWindow(s_window);
s_window = nullptr;
}
for (int i = 0; i < s_sharedCtxCount; i++) {
if (s_sharedCtxs[i]) SDL_GL_DeleteContext(s_sharedCtxs[i]);
if (s_sharedWins[i]) SDL_DestroyWindow(s_sharedWins[i]);
}
SDL_Quit();
}
void C4JRender::DrawVertices(ePrimitiveType ptype, int count, void* dataIn,
eVertexType vType, ePixelShaderType) {
if (count <= 0 || !dataIn) return;
bool wasQuad = isQuadPrim((int)ptype);
GLenum glMode = mapPrim((int)ptype);
static thread_local std::vector<uint8_t> stdData;
static thread_local std::vector<uint8_t> triData;
stdData.clear();
triData.clear();
if (vType == VERTEX_TYPE_COMPRESSED) {
stdData.resize((size_t)count * 32);
const int16_t* src = (const int16_t*)dataIn;
uint8_t* dst = stdData.data();
for (int i = 0; i < count; i++) {
float* dstF = (float*)dst;
// Position: int16 / 1024
dstF[0] = src[0] / 1024.0f;
dstF[1] = src[1] / 1024.0f;
dstF[2] = src[2] / 1024.0f;
// int16 / 8192
dstF[3] = src[4] / 8192.0f;
dstF[4] = src[5] / 8192.0f;
// RGB565 32768
{
uint16_t packed = (uint16_t)((int)src[3] + 32768);
dst[20] = 255;
dst[21] = (uint8_t)((packed & 0x1F) * 255 / 31); // B
dst[22] = (uint8_t)(((packed >> 5) & 0x3F) * 255 / 63); // G
dst[23] = (uint8_t)(((packed >> 11) & 0x1F) * 255 / 31); // R
}
dst[24] = 0;
dst[25] = 127; // +Y (up)
dst[26] = 0;
dst[27] = 0;
// Lightmap
{
int16_t* dstS = (int16_t*)(dst + 28);
dstS[0] = src[6];
dstS[1] = src[7];
}
src += 8;
dst += 32;
}
dataIn = stdData.data();
}
static const size_t stride = 32;
if (wasQuad) {
int numQuads = count / 4;
int triVerts = numQuads * 6;
triData.resize((size_t)triVerts * stride);
const uint8_t* src = (const uint8_t*)dataIn;
uint8_t* dst = triData.data();
for (int q = 0; q < numQuads; q++) {
const uint8_t* v0 = src + (q * 4 + 0) * stride;
const uint8_t* v1 = src + (q * 4 + 1) * stride;
const uint8_t* v2 = src + (q * 4 + 2) * stride;
const uint8_t* v3 = src + (q * 4 + 3) * stride;
// Triangle 1: 0,1,2
memcpy(dst + 0 * stride, v0, stride);
memcpy(dst + 1 * stride, v1, stride);
memcpy(dst + 2 * stride, v2, stride);
// Triangle 2: 0,2,3
memcpy(dst + 3 * stride, v0, stride);
memcpy(dst + 4 * stride, v2, stride);
memcpy(dst + 5 * stride, v3, stride);
dst += 6 * stride;
}
dataIn = triData.data();
count = triVerts;
glMode = GL_TRIANGLES;
}
size_t bytes = (size_t)count * stride;
if (s_recListId >= 0) {
int first = (int)(s_recVerts.size() / stride);
s_recVerts.insert(s_recVerts.end(), (const uint8_t*)dataIn,
(const uint8_t*)dataIn + bytes);
s_recDraws.push_back({glMode, first, (GLsizei)count});
return;
}
pthread_mutex_lock(&s_glCallMtx);
pushRenderState();
glBindVertexArray(s_sVAO_std);
glBindBuffer(GL_ARRAY_BUFFER, s_sVBO_std);
// orphan buffer
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)bytes, nullptr, GL_STREAM_DRAW);
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)bytes, dataIn, GL_STREAM_DRAW);
bindStdAttribs();
glDrawArrays(glMode, 0, count);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
pthread_mutex_unlock(&s_glCallMtx);
}
void C4JRender::ReadPixels(int x, int y, int w, int h, void* buf) {
if (!buf) return;
pthread_mutex_lock(&s_glCallMtx);
glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buf);
pthread_mutex_unlock(&s_glCallMtx);
}
int C4JRender::CBuffCreate(int count) {
pthread_mutex_lock(&s_glCallMtx);
int b = s_nextListBase;
s_nextListBase += count;
pthread_mutex_unlock(&s_glCallMtx);
return b;
}
void C4JRender::CBuffDelete(int first, int count) {
pthread_mutex_lock(&s_glCallMtx);
for (int i = first; i < first + count; i++) {
auto it = s_chunkPool.find(i);
if (it != s_chunkPool.end()) {
it->second.destroy();
s_chunkPool.erase(it);
}
}
pthread_mutex_unlock(&s_glCallMtx);
}
void C4JRender::CBuffStart(int index, bool) {
s_recListId = index;
s_recVerts.clear();
s_recDraws.clear();
}
void C4JRender::CBuffEnd() {
if (s_recListId < 0) return;
pthread_mutex_lock(&s_glCallMtx);
ChunkBuffer& cb = s_chunkPool[s_recListId];
cb.destroy();
if (s_recVerts.empty()) {
s_chunkPool.erase(s_recListId);
pthread_mutex_unlock(&s_glCallMtx);
s_recListId = -1;
return;
}
cb.rawVerts = std::move(s_recVerts);
cb.draws = std::move(s_recDraws);
cb.valid = true;
cb.vboReady = false;
pthread_mutex_unlock(&s_glCallMtx);
s_recListId = -1;
}
void C4JRender::CBuffClear(int index) {
pthread_mutex_lock(&s_glCallMtx);
auto it = s_chunkPool.find(index);
if (it != s_chunkPool.end()) it->second.destroy();
pthread_mutex_unlock(&s_glCallMtx);
}
bool C4JRender::CBuffCall(int index, bool) {
pthread_mutex_lock(&s_glCallMtx);
auto it = s_chunkPool.find(index);
if (it == s_chunkPool.end() || !it->second.valid) {
pthread_mutex_unlock(&s_glCallMtx);
return false;
}
ChunkBuffer& cb = it->second;
if (!cb.vboReady) {
if (cb.rawVerts.empty()) {
pthread_mutex_unlock(&s_glCallMtx);
return false;
}
glGenVertexArrays(1, &cb.vao);
glGenBuffers(1, &cb.vbo);
glBindVertexArray(cb.vao);
glBindBuffer(GL_ARRAY_BUFFER, cb.vbo);
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cb.rawVerts.size(),
cb.rawVerts.data(), GL_STATIC_DRAW);
bindStdAttribs(); // single time bindstdattrib
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
cb.rawVerts.clear();
cb.rawVerts.shrink_to_fit();
cb.vboReady = true;
}
pushRenderState();
glBindVertexArray(cb.vao);
for (const auto& dc : cb.draws) glDrawArrays(dc.prim, dc.first, dc.count);
glBindVertexArray(0);
pthread_mutex_unlock(&s_glCallMtx);
return true;
}
void C4JRender::MatrixMode(int t) {
if (t == GL_PROJECTION)
s_matMode = 1;
else if (t == GL_TEXTURE)
s_matMode = 2;
else
s_matMode = 0;
}
void C4JRender::MatrixSetIdentity() {
activeStack().load(glm::mat4(1.f));
if (s_matMode == 0) markNormalDirty();
}
void C4JRender::MatrixPush() {
activeStack().push();
// push doesn't change cur() so no dirty needed but mark anyway to be safe
// ;w;
if (s_matMode == 0) markNormalDirty();
}
void C4JRender::MatrixPop() {
activeStack().pop();
if (s_matMode == 0) markNormalDirty();
}
void C4JRender::MatrixTranslate(float x, float y, float z) {
activeStack().mul(glm::translate(glm::mat4(1.f), {x, y, z}));
if (s_matMode == 0) markNormalDirty();
}
void C4JRender::MatrixRotate(float a, float x, float y, float z) {
activeStack().mul(glm::rotate(glm::mat4(1.f), a, {x, y, z}));
if (s_matMode == 0) markNormalDirty();
}
void C4JRender::MatrixScale(float x, float y, float z) {
activeStack().mul(glm::scale(glm::mat4(1.f), {x, y, z}));
if (s_matMode == 0) markNormalDirty();
}
void C4JRender::MatrixPerspective(float fovy, float asp, float zn, float zf) {
s_proj.cur() = glm::perspective(glm::radians(fovy), asp, zn, zf);
}
void C4JRender::MatrixOrthogonal(float l, float r, float b, float t, float zn,
float zf) {
s_proj.cur() = glm::ortho(l, r, b, t, zn, zf);
}
void C4JRender::MatrixMult(float* m) {
activeStack().mul(glm::make_mat4(m));
if (s_matMode == 0) markNormalDirty();
}
const float* C4JRender::MatrixGet(int t) {
static float buf[16];
glm::mat4* m = (t == GL_MODELVIEW_MATRIX) ? &s_mv.cur()
: (t == GL_PROJECTION_MATRIX) ? &s_proj.cur()
: nullptr;
if (m) memcpy(buf, glm::value_ptr(*m), 64);
return buf;
}
void C4JRender::Set_matrixDirty() {
// iggy wipes opengl state
s_boundProgram = 0;
s_gpu_state_valid = false;
s_normalMatDirty = true; // normal matrix dirt after iggy reset
if (s_shader.prog) {
glUseProgram(s_shader.prog);
s_boundProgram = s_shader.prog;
}
}
void C4JRender::Clear(int f) { glClear(f); }
void C4JRender::SetClearColour(const float c[4]) {
glClearColor(c[0], c[1], c[2], c[3]);
}
bool C4JRender::IsWidescreen() { return true; }
bool C4JRender::IsHiDef() { return true; }
void C4JRender::StateSetColour(float r, float g, float b, float a) {
s_rs.baseColor = {r, g, b, a};
}
void C4JRender::SetChunkOffset(float x, float y, float z) {
if (s_shader.uChunkOffset >= 0) glUniform3f(s_shader.uChunkOffset, x, y, z);
}
void C4JRender::StateSetDepthMask(bool e) {
glDepthMask(e ? GL_TRUE : GL_FALSE);
}
void C4JRender::StateSetBlendEnable(bool e) {
if (e)
glEnable(GL_BLEND);
else
glDisable(GL_BLEND);
}
void C4JRender::StateSetBlendFunc(int s, int d) { glBlendFunc(s, d); }
void C4JRender::StateSetDepthFunc(int f) { glDepthFunc(f); }
void C4JRender::StateSetFaceCull(bool e) {
if (e)
glEnable(GL_CULL_FACE);
else
glDisable(GL_CULL_FACE);
}
void C4JRender::StateSetFaceCullCW(bool e) { glFrontFace(e ? GL_CW : GL_CCW); }
void C4JRender::StateSetLineWidth(float w) {
#ifndef GLES
glLineWidth(w);
#else
(void)w;
#endif
}
void C4JRender::StateSetWriteEnable(bool r, bool g, bool b, bool a) {
glColorMask(r, g, b, a);
}
void C4JRender::StateSetDepthTestEnable(bool e) {
if (e)
glEnable(GL_DEPTH_TEST);
else
glDisable(GL_DEPTH_TEST);
}
void C4JRender::StateSetAlphaTestEnable(bool e) {
s_rs.alphaRef = e ? 0.1f : 0.f;
}
void C4JRender::StateSetAlphaFunc(int, float p) { s_rs.alphaRef = p; }
void C4JRender::StateSetDepthSlopeAndBias(float s, float b) {
if (s || b) {
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(s, b);
} else {
glDisable(GL_POLYGON_OFFSET_FILL);
}
}
void C4JRender::StateSetBlendFactor(unsigned int col) {
float a = ((col >> 24) & 0xFF) / 255.f;
float r = ((col >> 16) & 0xFF) / 255.f;
float g = ((col >> 8) & 0xFF) / 255.f;
float b = (col & 0xFF) / 255.f;
glBlendColor(r, g, b, a);
}
void C4JRender::StateSetFogEnable(bool e) { s_rs.fogEnable = e; }
void C4JRender::StateSetFogMode(int mode) {
s_rs.fogMode = (mode == GL_LINEAR) ? 1
: (mode == GL_EXP) ? 2
: (mode == 0x0801) ? 3
: 0;
}
void C4JRender::StateSetFogNearDistance(float d) { s_rs.fogStart = d; }
void C4JRender::StateSetFogFarDistance(float d) { s_rs.fogEnd = d; }
void C4JRender::StateSetFogDensity(float d) { s_rs.fogDensity = d; }
void C4JRender::StateSetFogColour(float r, float g, float b) {
s_rs.fogColor = {r, g, b, 1};
}
void C4JRender::StateSetLightingEnable(bool e) {
s_rs.lighting = e;
s_rs_dirty = true;
}
void C4JRender::StateSetLightColour(int, float r, float g, float b) {
s_rs.ldiff = {r, g, b};
s_rs_dirty = true;
}
void C4JRender::StateSetLightAmbientColour(float r, float g, float b) {
s_rs.lamb = {r, g, b};
s_rs_dirty = true;
}
void C4JRender::StateSetLightDirection(int light, float x, float y, float z) {
glm::vec3 d = glm::normalize(glm::mat3(s_mv.cur()) * glm::vec3(x, y, z));
if (light == 0)
s_rs.l0 = d;
else
s_rs.l1 = d;
}
void C4JRender::StateSetViewport(eViewportType) {
glViewport(0, 0, s_windowWidth, s_windowHeight);
}
void C4JRender::StateSetVertexTextureUV(float u, float v) {
s_rs.globalLM = {u, v};
}
void C4JRender::StateSetStencil(int fn, uint8_t ref, uint8_t fmask,
uint8_t wmask) {
glEnable(GL_STENCIL_TEST);
glStencilFunc(fn, ref, fmask);
glStencilMask(wmask);
}
void C4JRender::StateSetTextureEnable(bool e) {
if (s_rs.activeTexture == 0) {
s_rs.useTexture = e;
if (s_shader.prog) {
glUseProgram(s_shader.prog);
s_boundProgram = s_shader.prog;
glUniform1i(s_shader.uUseTexture, e ? 1 : 0);
}
}
}
void C4JRender::StateSetActiveTexture(int tex) {
s_rs.activeTexture = (tex == 0x84C1 /*GL_TEXTURE1*/) ? 1 : 0;
}
int C4JRender::TextureCreate() {
GLuint id;
glGenTextures(1, &id);
return (int)id;
}
void C4JRender::TextureFree(int i) {
GLuint id = (GLuint)i;
glDeleteTextures(1, &id);
}
void C4JRender::TextureBind(int idx) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, idx < 0 ? 0 : (GLuint)idx);
}
void C4JRender::TextureBindVertex(int idx, bool scaleLight) {
if (idx < 0) {
s_rs.useLightmap = false;
glActiveTexture(GL_TEXTURE0);
return;
}
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, (GLuint)idx);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glActiveTexture(GL_TEXTURE0);
s_rs.useLightmap = true;
s_rs.lmt = scaleLight ? glm::vec4{1.f, 1.f, 8.f / 256.f, 8.f / 256.f}
: glm::vec4{1.f, 1.f, 0.f, 0.f};
}
void C4JRender::TextureSetTextureLevels(int l) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, l > 0 ? l - 1 : 0);
if (l > 1)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_NEAREST_MIPMAP_LINEAR);
else
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
int C4JRender::TextureGetTextureLevels() { return 1; }
void C4JRender::TextureData(int w, int h, void* d, int lvl, eTextureFormat) {
glTexImage2D(GL_TEXTURE_2D, lvl, GL_RGBA, w, h, 0, GL_RGBA,
GL_UNSIGNED_BYTE, d);
if (lvl == 0) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
GLint maxLvl = 0;
glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, &maxLvl);
if (maxLvl == 0)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
}
void C4JRender::TextureDataUpdate(int xo, int yo, int w, int h, void* d,
int lvl) {
glTexSubImage2D(GL_TEXTURE_2D, lvl, xo, yo, w, h, GL_RGBA, GL_UNSIGNED_BYTE,
d);
}
void C4JRender::TextureSetParam(int p, int v) {
glTexParameteri(GL_TEXTURE_2D, p, v);
}
static int stbLoad(unsigned char* data, int w, int h, D3DXIMAGE_INFO* info,
int** out) {
int* px = new int[w * h];
for (int i = 0; i < w * h; i++) {
unsigned char r = data[i * 4], g = data[i * 4 + 1], b = data[i * 4 + 2],
a = data[i * 4 + 3];
px[i] = (a << 24) | (r << 16) | (g << 8) | b;
}
if (info) {
info->Width = w;
info->Height = h;
}
*out = px;
return 0; // Success
}
int C4JRender::LoadTextureData(const char* fn, D3DXIMAGE_INFO* i, int** o) {
int w, h, c;
unsigned char* d = stbi_load(fn, &w, &h, &c, 4);
if (!d) return -1; // Failure
int hr = stbLoad(d, w, h, i, o);
stbi_image_free(d);
return hr;
}
int C4JRender::LoadTextureData(uint8_t* pb, uint32_t nb, D3DXIMAGE_INFO* i,
int** o) {
int w, h, c;
unsigned char* d = stbi_load_from_memory(pb, (int)nb, &w, &h, &c, 4);
if (!d) return -1; // Failure
int hr = stbLoad(d, w, h, i, o);
stbi_image_free(d);
return hr;
}
void C4JRender::UpdateGamma(unsigned short usGamma) {
constexpr unsigned short GAMMA_MAX = 32768;
s_rs.gamma = 0.5f + ((float)(usGamma) * (1.0f / GAMMA_MAX));
}
// MARK: C hooks
int glGenTextures_4J() {
GLuint id = 0;
::glGenTextures(1, &id);
return (int)id;
}
void glGenTextures_4J(int n, unsigned int* textures) {
::glGenTextures(n, textures);
}
void glDeleteTextures_4J(int id) {
GLuint uid = (GLuint)id;
::glDeleteTextures(1, &uid);
}
void glDeleteTextures_4J(int n, const unsigned int* textures) {
::glDeleteTextures(n, textures);
}
void glBeginQuery_4J_Helper(unsigned int target, unsigned int id) {
typedef void (*PFNGLBEGINQUERYPROC)(unsigned int, unsigned int);
static PFNGLBEGINQUERYPROC fn =
(PFNGLBEGINQUERYPROC)dlsym(RTLD_DEFAULT, "glBeginQuery");
if (fn) fn(target, id);
}
void glEndQuery_4J_Helper(unsigned int target) {
typedef void (*PFNGLENDQUERYPROC)(unsigned int);
static PFNGLENDQUERYPROC fn =
(PFNGLENDQUERYPROC)dlsym(RTLD_DEFAULT, "glEndQuery");
if (fn) fn(target);
}
void glGenQueries_4J_Helper(unsigned int* id) {
#ifdef GLES
glGenQueries(1, id);
#else
typedef void (*PFNGLGENQUERIESPROC)(int, unsigned int*);
static PFNGLGENQUERIESPROC fn =
(PFNGLGENQUERIESPROC)dlsym(RTLD_DEFAULT, "glGenQueries");
if (fn) fn(1, id);
#endif
}
void glGetQueryObjectu_4J_Helper(unsigned int id, unsigned int pname,
unsigned int* val) {
#ifdef GLES
glGetQueryObjectuiv(id, pname, val);
#else
typedef void (*PFNGLGETQUERYOBJECTUIVPROC)(unsigned int, unsigned int,
unsigned int*);
static PFNGLGETQUERYOBJECTUIVPROC fn =
(PFNGLGETQUERYOBJECTUIVPROC)dlsym(RTLD_DEFAULT, "glGetQueryObjectuiv");
if (fn) fn(id, pname, val);
#endif
}
// c hooks
#undef glFogfv
#undef glLightfv
#undef glLightModelfv
#undef glShadeModel
#undef glColorMaterial
#undef glNormal3f
extern "C" {
void glFogfv(GLenum pname, const GLfloat* params) {
if (pname == 0x0B66)
RenderManager.StateSetFogColour(params[0], params[1], params[2]);
}
void glLightfv(GLenum light, GLenum pname, const GLfloat* params) {
if (pname == 0x1203)
RenderManager.StateSetLightDirection(light == 0x4000 ? 0 : 1, params[0],
params[1], params[2]);
else if (pname == 0x1200)
RenderManager.StateSetLightAmbientColour(params[0], params[1],
params[2]);
else if (pname == 0x1201)
RenderManager.StateSetLightColour(light == 0x4000 ? 0 : 1, params[0],
params[1], params[2]);
}
void glLightModelfv(GLenum pname, const GLfloat* params) {
if (pname == 0x0B53)
RenderManager.StateSetLightAmbientColour(params[0], params[1],
params[2]);
}
void glShadeModel(GLenum) {}
void glColorMaterial(GLenum, GLenum) {}
void glNormal3f(GLfloat, GLfloat, GLfloat) {}
}