Porting to WASM
This commit is contained in:
@@ -1,10 +1,3 @@
|
||||
struct Shader {
|
||||
uint32_t pipeline;
|
||||
uint32_t fshader;
|
||||
uint32_t vshader;
|
||||
};
|
||||
Shader CreateShader(char *glsl_vshader, char *glsl_fshader);
|
||||
|
||||
struct Vertex2D {
|
||||
Vec2 pos;
|
||||
Vec2 tex;
|
||||
@@ -23,6 +16,12 @@ struct VertexList2D {
|
||||
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;
|
||||
|
||||
@@ -35,6 +34,155 @@ 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;
|
||||
@@ -43,6 +191,7 @@ void BeginFrameRender(float wx, float wy) {
|
||||
CurrentScissor = Rect0Size(wx, wy);
|
||||
}
|
||||
|
||||
// ---------- EndFrameRender for ES3 ----------
|
||||
void EndFrameRender(float wx, float wy, Color color) {
|
||||
glEnable(GL_BLEND);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
@@ -55,131 +204,47 @@ void EndFrameRender(float wx, float wy, Color color) {
|
||||
glClearColor(color.r, color.g, color.b, color.a);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
// Default draw using the font texture
|
||||
glBindProgramPipeline(Shader2D.pipeline);
|
||||
// Use program and set uniforms
|
||||
glUseProgram(Shader2D.program);
|
||||
float xinverse = 1.f / (wx / 2.f);
|
||||
float yinverse = 1.f / (wy / 2.f);
|
||||
glProgramUniform2f(Shader2D.vshader, 0, xinverse, yinverse);
|
||||
// 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);
|
||||
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);
|
||||
glNamedBufferSubData(VBO, 0, it->count * sizeof(Vertex2D), it->vertices);
|
||||
glBindVertexArray(VAO);
|
||||
GLint s_texture = 0; // texture unit that sampler2D will use in GLSL code
|
||||
glBindTextureUnit(s_texture, MainFont.texture_id);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void InitRender() {
|
||||
InitArena(&RenderArena);
|
||||
|
||||
glDebugMessageCallback(&GLDebugCallback, NULL);
|
||||
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
|
||||
|
||||
glCreateBuffers(1, &VBO);
|
||||
glNamedBufferStorage(VBO, Lengthof(VertexNode2D::vertices) * sizeof(Vertex2D), 0, GL_DYNAMIC_STORAGE_BIT);
|
||||
|
||||
glCreateVertexArrays(1, &VAO);
|
||||
|
||||
GLint vbuf_index = 0;
|
||||
glVertexArrayVertexBuffer(VAO, vbuf_index, VBO, 0, sizeof(struct Vertex2D));
|
||||
|
||||
GLint a_pos = 0;
|
||||
glVertexArrayAttribFormat(VAO, a_pos, 2, GL_FLOAT, GL_FALSE, offsetof(struct Vertex2D, pos));
|
||||
glVertexArrayAttribBinding(VAO, a_pos, vbuf_index);
|
||||
glEnableVertexArrayAttrib(VAO, a_pos);
|
||||
|
||||
GLint a_tex = 1;
|
||||
glVertexArrayAttribFormat(VAO, a_tex, 2, GL_FLOAT, GL_FALSE, offsetof(struct Vertex2D, tex));
|
||||
glVertexArrayAttribBinding(VAO, a_tex, vbuf_index);
|
||||
glEnableVertexArrayAttrib(VAO, a_tex);
|
||||
|
||||
GLint a_color = 2;
|
||||
glVertexArrayAttribFormat(VAO, a_color, 4, GL_UNSIGNED_BYTE, GL_FALSE, offsetof(struct Vertex2D, color));
|
||||
glVertexArrayAttribBinding(VAO, a_color, vbuf_index);
|
||||
glEnableVertexArrayAttrib(VAO, a_color);
|
||||
|
||||
char *glsl_vshader = R"==(
|
||||
#version 450 core
|
||||
|
||||
layout(location=0) uniform vec2 U_InvHalfScreenSize;
|
||||
layout(location=0) in vec2 VPos;
|
||||
layout(location=1) in vec2 VTex;
|
||||
layout(location=2) in vec4 VColor;
|
||||
|
||||
out gl_PerVertex { vec4 gl_Position; }; // required because of ARB_separate_shader_objects
|
||||
out vec2 FUV;
|
||||
out vec4 FColor;
|
||||
|
||||
void main() {
|
||||
vec2 pos = VPos * U_InvHalfScreenSize;
|
||||
pos.y = 2 - pos.y; // invert y axis
|
||||
pos -= vec2(1, 1); // convert to 0,1 range
|
||||
|
||||
gl_Position = vec4(pos, 0, 1);
|
||||
FUV = VTex;
|
||||
|
||||
FColor = VColor / 255.0;
|
||||
}
|
||||
)==";
|
||||
|
||||
char *glsl_fshader = R"==(
|
||||
#version 450 core
|
||||
|
||||
in vec2 FUV;
|
||||
in vec4 FColor;
|
||||
|
||||
layout (binding=0) uniform sampler2D S_Texture;
|
||||
layout (location=0) out vec4 OutColor;
|
||||
|
||||
void main() {
|
||||
vec4 c = FColor;
|
||||
c.a *= texture(S_Texture, FUV).r;
|
||||
OutColor = c;
|
||||
}
|
||||
)==";
|
||||
|
||||
Shader2D = CreateShader(glsl_vshader, glsl_fshader);
|
||||
}
|
||||
|
||||
Shader CreateShader(char *glsl_vshader, char *glsl_fshader) {
|
||||
Shader result = {};
|
||||
result.vshader = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &glsl_vshader);
|
||||
result.fshader = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &glsl_fshader);
|
||||
|
||||
GLint linked;
|
||||
glGetProgramiv(result.vshader, GL_LINK_STATUS, &linked);
|
||||
if (!linked) {
|
||||
char message[1024];
|
||||
glGetProgramInfoLog(result.vshader, sizeof(message), NULL, message);
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Failed to create vertex shader!", message, NULL);
|
||||
}
|
||||
|
||||
glGetProgramiv(result.fshader, GL_LINK_STATUS, &linked);
|
||||
if (!linked) {
|
||||
char message[1024];
|
||||
glGetProgramInfoLog(result.fshader, sizeof(message), NULL, message);
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Failed to create fragment shader!", message, NULL);
|
||||
}
|
||||
|
||||
glGenProgramPipelines(1, &result.pipeline);
|
||||
glUseProgramStages(result.pipeline, GL_VERTEX_SHADER_BIT, result.vshader);
|
||||
glUseProgramStages(result.pipeline, GL_FRAGMENT_SHADER_BIT, result.fshader);
|
||||
return result;
|
||||
}
|
||||
|
||||
VertexNode2D *AllocVertexNode2D(Allocator allocator, VertexList2D *list) {
|
||||
VertexNode2D *node = AllocType(allocator, VertexNode2D);
|
||||
@@ -352,9 +417,11 @@ void DrawCircle(Vec2 pos, float radius, Color color) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------- ReloadFont - replace DSA texture calls ----------
|
||||
void ReloadFont() {
|
||||
Int size = StyleFontSize;
|
||||
size = ClampBottom((Int)2, size);
|
||||
size = ClampBottom((Int)2, size);
|
||||
if (MainFont.texture_id) {
|
||||
glDeleteTextures(1, &MainFont.texture_id);
|
||||
Dealloc(&MainFont.glyphs);
|
||||
@@ -362,20 +429,25 @@ void ReloadFont() {
|
||||
}
|
||||
|
||||
Scratch scratch;
|
||||
Atlas atlas = CreateAtlas(scratch, {2048, 2048});
|
||||
MainFont = CreateFont(&atlas, (uint32_t)size, StyleFont);
|
||||
{
|
||||
GLint filter = GL_NEAREST;
|
||||
if (StyleFontFilter == 1) filter = GL_LINEAR;
|
||||
glCreateTextures(GL_TEXTURE_2D, 1, &atlas.texture_id);
|
||||
glTextureParameteri(atlas.texture_id, GL_TEXTURE_MIN_FILTER, filter);
|
||||
glTextureParameteri(atlas.texture_id, GL_TEXTURE_MAG_FILTER, filter);
|
||||
glTextureParameteri(atlas.texture_id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTextureParameteri(atlas.texture_id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTextureStorage2D(atlas.texture_id, 1, GL_R8, (GLsizei)atlas.size.x, (GLsizei)atlas.size.y);
|
||||
glTextureSubImage2D(atlas.texture_id, 0, 0, 0, (GLsizei)atlas.size.x, (GLsizei)atlas.size.y, GL_RED, GL_UNSIGNED_BYTE, atlas.bitmap);
|
||||
}
|
||||
MainFont.texture_id = atlas.texture_id;
|
||||
FontCharSpacing = GetCharSpacing(&MainFont);
|
||||
FontLineSpacing = GetLineSpacing(&MainFont);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user