454 lines
14 KiB
C++
454 lines
14 KiB
C++
struct Vertex2D {
|
|
Vec2 pos;
|
|
Vec2 tex;
|
|
Color color;
|
|
};
|
|
|
|
struct VertexNode2D {
|
|
VertexNode2D *next;
|
|
int count;
|
|
Vertex2D vertices[1024 * 16];
|
|
Rect2 scissor;
|
|
};
|
|
|
|
struct VertexList2D {
|
|
VertexNode2D *first;
|
|
VertexNode2D *last;
|
|
};
|
|
|
|
struct Shader {
|
|
GLuint program; // linked program (vertex+fragment)
|
|
GLint uni_invHalf; // uniform location for inv half-screen size
|
|
GLint uni_texture; // sampler location
|
|
};
|
|
|
|
VertexList2D Vertices;
|
|
int64_t TotalVertexCount;
|
|
|
|
unsigned VBO, VAO;
|
|
Shader Shader2D;
|
|
Arena RenderArena;
|
|
Rect2 CurrentScissor;
|
|
|
|
Font MainFont;
|
|
Int FontLineSpacing;
|
|
Int FontCharSpacing;
|
|
|
|
// ---------- shaders (ES3 / WebGL2) ----------
|
|
static const char *glsl_vshader_es3 = R"==(#version 300 es
|
|
precision highp float;
|
|
|
|
uniform vec2 U_InvHalfScreenSize; // same meaning as before
|
|
|
|
layout(location = 0) in vec2 aPos;
|
|
layout(location = 1) in vec2 aTex;
|
|
layout(location = 2) in vec4 aColor; // normalized unsigned byte will map to 0..1 if we use normalized=true
|
|
|
|
out vec2 vUV;
|
|
out vec4 vColor;
|
|
|
|
void main() {
|
|
vec2 pos = aPos * U_InvHalfScreenSize;
|
|
pos.y = 2.0 - pos.y; // invert Y the same way you had
|
|
pos -= vec2(1.0, 1.0);
|
|
gl_Position = vec4(pos, 0.0, 1.0);
|
|
vUV = aTex;
|
|
vColor = aColor; // already 0..1
|
|
}
|
|
)==";
|
|
|
|
static const char *glsl_fshader_es3 = R"==(#version 300 es
|
|
precision mediump float;
|
|
|
|
in vec2 vUV;
|
|
in vec4 vColor;
|
|
|
|
uniform sampler2D S_Texture;
|
|
out vec4 OutColor;
|
|
|
|
void main() {
|
|
float a = texture(S_Texture, vUV).r;
|
|
vec4 c = vColor;
|
|
c.a *= a;
|
|
OutColor = c;
|
|
}
|
|
)==";
|
|
|
|
void ReportWarningf(const char *fmt, ...);
|
|
void GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *user) {
|
|
ReportWarningf("OpenGL message: %s", message);
|
|
if (severity == GL_DEBUG_SEVERITY_HIGH || severity == GL_DEBUG_SEVERITY_MEDIUM) {
|
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "OpenGL error", message, NULL);
|
|
}
|
|
}
|
|
|
|
// ---------- helper: compile/link ----------
|
|
static GLuint CompileShaderSrc(GLenum kind, const char *src) {
|
|
GLuint s = glCreateShader(kind);
|
|
glShaderSource(s, 1, &src, NULL);
|
|
glCompileShader(s);
|
|
GLint ok = 0;
|
|
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
|
|
if (!ok) {
|
|
char buf[1024];
|
|
glGetShaderInfoLog(s, sizeof(buf), NULL, buf);
|
|
SDL_Log("%s", buf);
|
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
|
|
(kind == GL_VERTEX_SHADER ? "Vertex shader compile error" : "Fragment shader compile error"),
|
|
buf, NULL);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
Shader CreateShaderES3(const char *vsrc, const char *fsrc) {
|
|
Shader out = {};
|
|
GLuint vs = CompileShaderSrc(GL_VERTEX_SHADER, vsrc);
|
|
GLuint fs = CompileShaderSrc(GL_FRAGMENT_SHADER, fsrc);
|
|
|
|
GLuint prog = glCreateProgram();
|
|
glAttachShader(prog, vs);
|
|
glAttachShader(prog, fs);
|
|
|
|
// Bind attribute locations to match layout locations (optional because we use layout(location=...))
|
|
// glBindAttribLocation(prog, 0, "aPos");
|
|
// glBindAttribLocation(prog, 1, "aTex");
|
|
// glBindAttribLocation(prog, 2, "aColor");
|
|
|
|
glLinkProgram(prog);
|
|
GLint linked = 0;
|
|
glGetProgramiv(prog, GL_LINK_STATUS, &linked);
|
|
if (!linked) {
|
|
char buf[1024];
|
|
glGetProgramInfoLog(prog, sizeof(buf), NULL, buf);
|
|
SDL_Log("%s", buf);
|
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Program link error", buf, NULL);
|
|
}
|
|
|
|
// We can detach/delete shaders after linking
|
|
glDetachShader(prog, vs);
|
|
glDetachShader(prog, fs);
|
|
glDeleteShader(vs);
|
|
glDeleteShader(fs);
|
|
|
|
out.program = prog;
|
|
out.uni_invHalf = glGetUniformLocation(prog, "U_InvHalfScreenSize");
|
|
out.uni_texture = glGetUniformLocation(prog, "S_Texture");
|
|
|
|
return out;
|
|
}
|
|
|
|
// ---------- InitRender for ES3 ----------
|
|
void InitRender() {
|
|
#if OS_WASM
|
|
RenderArena = *AllocArena(GetSystemAllocator(), MiB(64));
|
|
#else
|
|
InitArena(&RenderArena);
|
|
#endif
|
|
|
|
#if !OS_WASM
|
|
glDebugMessageCallback(&GLDebugCallback, NULL);
|
|
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
|
|
#endif
|
|
|
|
// Create VBO (non-DSA path)
|
|
glGenBuffers(1, &VBO);
|
|
// reserve buffer big enough for one VertexNode2D->vertices array (same as original intent)
|
|
GLsizeiptr vbo_size = (GLsizeiptr) (Lengthof(((VertexNode2D*)0)->vertices) * sizeof(Vertex2D));
|
|
glBindBuffer(GL_ARRAY_BUFFER, VBO);
|
|
glBufferData(GL_ARRAY_BUFFER, vbo_size, NULL, GL_DYNAMIC_DRAW); // NULL initial, dynamic usage
|
|
|
|
// Create and setup VAO
|
|
glGenVertexArrays(1, &VAO);
|
|
glBindVertexArray(VAO);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, VBO);
|
|
// attribute 0 : pos (vec2, floats)
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (const void*)offsetof(Vertex2D, pos));
|
|
// attribute 1 : tex (vec2)
|
|
glEnableVertexAttribArray(1);
|
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (const void*)offsetof(Vertex2D, tex));
|
|
// attribute 2 : color (vec4) -- stored as 4 unsigned bytes; we want normalized floats 0..1
|
|
glEnableVertexAttribArray(2);
|
|
// Using normalized GL_TRUE to map 0..255 -> 0..1 directly in shader
|
|
glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex2D), (const void*)offsetof(Vertex2D, color));
|
|
|
|
// Unbind VAO and VBO
|
|
glBindVertexArray(0);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
// Create shader program (ES3)
|
|
Shader2D = CreateShaderES3(glsl_vshader_es3, glsl_fshader_es3);
|
|
|
|
// other initializations like fonts will be done elsewhere (ReloadFont)
|
|
}
|
|
|
|
void BeginFrameRender(float wx, float wy) {
|
|
Clear(&RenderArena);
|
|
TotalVertexCount = 0;
|
|
Vertices.first = NULL;
|
|
Vertices.last = NULL;
|
|
CurrentScissor = Rect0Size(wx, wy);
|
|
}
|
|
|
|
// ---------- EndFrameRender for ES3 ----------
|
|
void EndFrameRender(float wx, float wy, Color color) {
|
|
glEnable(GL_BLEND);
|
|
glEnable(GL_SCISSOR_TEST);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDisable(GL_CULL_FACE);
|
|
|
|
glViewport(0, 0, (GLsizei)wx, (GLsizei)wy);
|
|
glScissor(0, 0, (GLsizei)wx, (GLsizei)wy);
|
|
glClearColor(color.r, color.g, color.b, color.a);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
|
|
|
// Use program and set uniforms
|
|
glUseProgram(Shader2D.program);
|
|
float xinverse = 1.f / (wx / 2.f);
|
|
float yinverse = 1.f / (wy / 2.f);
|
|
// set uniform (U_InvHalfScreenSize)
|
|
if (Shader2D.uni_invHalf >= 0) {
|
|
glUniform2f(Shader2D.uni_invHalf, xinverse, yinverse);
|
|
}
|
|
// set sampler to texture unit 0
|
|
if (Shader2D.uni_texture >= 0) {
|
|
glUniform1i(Shader2D.uni_texture, 0);
|
|
}
|
|
|
|
glBindVertexArray(VAO);
|
|
for (VertexNode2D *it = Vertices.first; it; it = it->next) {
|
|
Rect2 rect = it->scissor;
|
|
GLint x = (GLint)rect.min.x;
|
|
GLint y = (GLint)rect.min.y;
|
|
GLsizei w = (GLsizei)(rect.max.x - x);
|
|
GLsizei h = (GLsizei)(rect.max.y - y);
|
|
// convert scissor Y to OpenGL bottom-left origin like before
|
|
glScissor(x, (GLint)wy - (GLint)rect.max.y, w, h);
|
|
|
|
// upload vertex data into VBO at offset 0
|
|
glBindBuffer(GL_ARRAY_BUFFER, VBO);
|
|
glBufferSubData(GL_ARRAY_BUFFER, 0, it->count * sizeof(Vertex2D), it->vertices);
|
|
|
|
// bind texture unit 0 and the font texture
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, MainFont.texture_id);
|
|
|
|
// draw
|
|
glDrawArrays(GL_TRIANGLES, 0, it->count);
|
|
|
|
// unbind VBO (optional)
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
}
|
|
glBindVertexArray(0);
|
|
glUseProgram(0);
|
|
}
|
|
|
|
|
|
VertexNode2D *AllocVertexNode2D(Allocator allocator, VertexList2D *list) {
|
|
VertexNode2D *node = AllocType(allocator, VertexNode2D);
|
|
SLL_QUEUE_ADD(list->first, list->last, node);
|
|
return node;
|
|
}
|
|
|
|
void SetScissor(Rect2 rect) {
|
|
CurrentScissor = rect;
|
|
|
|
VertexNode2D *node = Vertices.last;
|
|
if (!node) {
|
|
node = AllocVertexNode2D(RenderArena, &Vertices);
|
|
node->scissor = rect;
|
|
return;
|
|
}
|
|
|
|
if (node->scissor != rect && node->count == 0) {
|
|
node->scissor = rect;
|
|
return;
|
|
}
|
|
|
|
if (node->scissor != rect) {
|
|
node = AllocVertexNode2D(RenderArena, &Vertices);
|
|
node->scissor = rect;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void SetScissor(Rect2I rect) { SetScissor(ToRect2(rect)); }
|
|
|
|
Vertex2D *AllocVertex2D(Allocator allocator, VertexList2D *list, int count) {
|
|
VertexNode2D *node = list->last;
|
|
if (node == 0 || node->count + count > Lengthof(node->vertices)) {
|
|
node = AllocVertexNode2D(allocator, list);
|
|
node->scissor = CurrentScissor;
|
|
}
|
|
|
|
TotalVertexCount += count;
|
|
Vertex2D *result = node->vertices + node->count;
|
|
node->count += count;
|
|
return result;
|
|
}
|
|
|
|
void PushVertex2D(Allocator allocator, VertexList2D *list, Vertex2D *vertices, int count) {
|
|
Vertex2D *result = AllocVertex2D(allocator, list, count);
|
|
for (int i = 0; i < count; i += 1) result[i] = vertices[i];
|
|
}
|
|
|
|
void PushQuad2D(Allocator arena, VertexList2D *list, Rect2 rect, Rect2 tex, Color color, float rotation = 0.f, Vec2 rotation_point = {}) {
|
|
Vertex2D *v = AllocVertex2D(arena, list, 6);
|
|
v[0] = {
|
|
{rect.min.x, rect.max.y},
|
|
{ tex.min.x, tex.max.y},
|
|
color
|
|
};
|
|
v[1] = {
|
|
{rect.max.x, rect.max.y},
|
|
{ tex.max.x, tex.max.y},
|
|
color
|
|
};
|
|
v[2] = {
|
|
{rect.min.x, rect.min.y},
|
|
{ tex.min.x, tex.min.y},
|
|
color
|
|
};
|
|
v[3] = {
|
|
{rect.min.x, rect.min.y},
|
|
{ tex.min.x, tex.min.y},
|
|
color
|
|
};
|
|
v[4] = {
|
|
{rect.max.x, rect.max.y},
|
|
{ tex.max.x, tex.max.y},
|
|
color
|
|
};
|
|
v[5] = {
|
|
{rect.max.x, rect.min.y},
|
|
{ tex.max.x, tex.min.y},
|
|
color
|
|
};
|
|
if (rotation != 0.f) {
|
|
float s = sinf(rotation);
|
|
float c = cosf(rotation);
|
|
for (int i = 0; i < 6; i += 1) {
|
|
v[i].pos -= rotation_point;
|
|
v[i].pos = {v[i].pos.x * c + v[i].pos.y * (-s), v[i].pos.x * s + v[i].pos.y * c};
|
|
v[i].pos += rotation_point;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawRect(Rect2 rect, Color color) {
|
|
PushQuad2D(RenderArena, &Vertices, rect, MainFont.white_texture_bounding_box, color);
|
|
}
|
|
void DrawRect(Rect2I rect, Color color) {
|
|
PushQuad2D(RenderArena, &Vertices, ToRect2(rect), MainFont.white_texture_bounding_box, color);
|
|
}
|
|
|
|
void DrawRectOutline(Rect2 rect, Color color, float thickness = 2) {
|
|
Rect2 a = CutLeft(&rect, thickness);
|
|
Rect2 b = CutRight(&rect, thickness);
|
|
Rect2 c = CutTop(&rect, thickness);
|
|
Rect2 d = CutBottom(&rect, thickness);
|
|
PushQuad2D(RenderArena, &Vertices, a, MainFont.white_texture_bounding_box, color);
|
|
PushQuad2D(RenderArena, &Vertices, b, MainFont.white_texture_bounding_box, color);
|
|
PushQuad2D(RenderArena, &Vertices, c, MainFont.white_texture_bounding_box, color);
|
|
PushQuad2D(RenderArena, &Vertices, d, MainFont.white_texture_bounding_box, color);
|
|
}
|
|
|
|
void DrawRectOutline(Rect2I rect, Color color, Int thickness = 2) {
|
|
DrawRectOutline(ToRect2(rect), color, (float)thickness);
|
|
}
|
|
|
|
Int GetCharSpacing(Font *font, int codepoint = '_') {
|
|
Glyph *g = GetGlyph(font, codepoint);
|
|
if (g->xadvance) return (Int)g->xadvance;
|
|
return (Int)g->size.x;
|
|
}
|
|
|
|
Int GetLineSpacing(Font *font) {
|
|
Int result = (Int)(font->ascent - font->descent + font->line_gap);
|
|
return result;
|
|
}
|
|
|
|
Vec2 DrawString(Font *font, String16 string, Vec2 pos, Color color, bool draw = true) {
|
|
pos.y += GetLineSpacing(font) + font->descent;
|
|
Vec2 original_pos = pos;
|
|
For(string) {
|
|
Glyph *g = GetGlyph(font, it);
|
|
Rect2 rect = Rect2FromSize(pos + g->offset, g->size);
|
|
if (draw && it != '\n' && it != ' ' && it != '\t') {
|
|
PushQuad2D(RenderArena, &Vertices, rect, g->atlas_bounding_box, color);
|
|
}
|
|
pos.x += g->xadvance;
|
|
}
|
|
Vec2 result = {pos.x - original_pos.x, font->size};
|
|
return result;
|
|
}
|
|
|
|
#define PI32 3.14159265359f
|
|
void DrawCircle(Vec2 pos, float radius, Color color) {
|
|
const int segment_count = 16;
|
|
const int vertex_count = segment_count * 3;
|
|
Vec2 points[segment_count + 1];
|
|
for (int i = 0; i < Lengthof(points); i += 1) {
|
|
float radians = 2.0f * PI32 * float(i) / float(segment_count);
|
|
float x = radius * cosf(radians);
|
|
float y = radius * sinf(radians);
|
|
points[i] = {x, y};
|
|
}
|
|
points[segment_count] = points[0]; // wrap around
|
|
|
|
Vertex2D *vertices = AllocVertex2D(RenderArena, &Vertices, vertex_count);
|
|
int point_i = 0;
|
|
int segment_i = 0;
|
|
for (; segment_i < vertex_count; segment_i += 3, point_i += 1) {
|
|
Rect2 tex = MainFont.white_texture_bounding_box;
|
|
Vertex2D *it = vertices + segment_i;
|
|
it[0].color = color;
|
|
it[1].color = color;
|
|
it[2].color = color;
|
|
it[0].tex = {tex.min.x, tex.max.y};
|
|
it[1].tex = {tex.max.x, tex.max.y};
|
|
it[2].tex = {tex.min.x, tex.min.y};
|
|
|
|
it[0].pos = {pos + points[point_i]};
|
|
it[1].pos = {pos + points[point_i + 1]};
|
|
it[2].pos = {pos};
|
|
}
|
|
}
|
|
|
|
|
|
// ---------- ReloadFont - replace DSA texture calls ----------
|
|
void ReloadFont() {
|
|
Int size = StyleFontSize;
|
|
size = ClampBottom((Int)2, size);
|
|
if (MainFont.texture_id) {
|
|
glDeleteTextures(1, &MainFont.texture_id);
|
|
Dealloc(&MainFont.glyphs);
|
|
MainFont = {};
|
|
}
|
|
|
|
Scratch scratch;
|
|
Atlas atlas = CreateAtlas(scratch, {2048, 2048});
|
|
MainFont = CreateFont(&atlas, (uint32_t)size, StyleFont);
|
|
|
|
// create and fill texture with ES3-compatible API
|
|
GLint filter = GL_NEAREST;
|
|
if (StyleFontFilter == 1) filter = GL_LINEAR;
|
|
|
|
GLuint tex = 0;
|
|
glGenTextures(1, &tex);
|
|
glBindTexture(GL_TEXTURE_2D, tex);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
// allocate storage and upload
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, (GLsizei)atlas.size.x, (GLsizei)atlas.size.y, 0, GL_RED, GL_UNSIGNED_BYTE, atlas.bitmap);
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
MainFont.texture_id = tex;
|
|
FontCharSpacing = GetCharSpacing(&MainFont);
|
|
FontLineSpacing = GetLineSpacing(&MainFont);
|
|
}
|